diff options
| author | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2026-02-20 10:41:45 +0700 |
|---|---|---|
| committer | FIN-IT_AndriFP <andrifebriyadiputra@gmail.com> | 2026-02-20 10:41:45 +0700 |
| commit | 8a34fef2321af2bd362e0649e24a5ce34c2702e3 (patch) | |
| tree | ed5d5605beb3367ac2fc36d047a0f8a58b40475b | |
| parent | 10525d407bbc34d79146ff22955a874509f5b204 (diff) | |
| parent | 7394cd71c05d56ebfd5a5f95518a849289a782e0 (diff) | |
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into pum-v4
| -rwxr-xr-x | indoteknik_custom/__manifest__.py | 9 | ||||
| -rwxr-xr-x | indoteknik_custom/models/__init__.py | 3 | ||||
| -rw-r--r-- | indoteknik_custom/models/keywords.py | 244 | ||||
| -rwxr-xr-x | indoteknik_custom/models/product_template.py | 202 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 3 | ||||
| -rw-r--r-- | indoteknik_custom/views/find_page.xml | 13 | ||||
| -rw-r--r-- | indoteknik_custom/views/keywords.xml | 96 |
7 files changed, 475 insertions, 95 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 51cfa592..36588967 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -90,7 +90,7 @@ 'views/product_sla.xml', 'views/voucher.xml', 'views/bill_receipt.xml', - 'views/account_financial_report_view.xml', + 'views/account_financial_report_view.xml', 'views/account_report_general_ledger_view.xml', 'views/account_move_multi_update.xml', 'views/airway_bill.xml', @@ -178,23 +178,20 @@ 'views/refund_sale_order.xml', 'views/advance_payment_request.xml', 'views/advance_payment_settlement.xml', - # 'views/refund_sale_order.xml', 'views/tukar_guling.xml', - # 'views/tukar_guling_return_views.xml' 'views/tukar_guling_po.xml', - # 'views/refund_sale_order.xml', 'views/update_date_planned_po_wizard_view.xml', 'views/unpaid_invoice_view.xml', 'views/letter_receivable.xml', 'views/letter_receivable_mail_template.xml', 'views/mail_template_pum.xml', - # 'views/reimburse.xml', 'views/sj_tele.xml', 'views/close_tempo_mail_template.xml', 'views/domain_apo.xml', 'views/uom_uom.xml', + 'views/update_depreciation_move_wizard_view.xml', 'views/commission_internal.xml', - 'views/update_depreciation_move_wizard_view.xml' + 'views/keywords.xml' ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 19a96bee..a042750b 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -165,4 +165,5 @@ from . import partial_delivery from . import domain_apo from . import uom_uom from . import commission_internal -from . import update_depreciation_move_wizard
\ No newline at end of file +from . import update_depreciation_move_wizard +from . import keywords diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py new file mode 100644 index 00000000..9c8e2cfd --- /dev/null +++ b/indoteknik_custom/models/keywords.py @@ -0,0 +1,244 @@ +from itertools import product +from multiprocessing import Condition +from odoo import fields, models, api, tools, _ +import logging +import re +import pysolr +from odoo.exceptions import UserError +import base64 +import xlrd, xlwt +import io + + +_logger = logging.getLogger(__name__) +solr = pysolr.Solr('http://10.148.0.5:8983/solr/searchkey/', always_commit=True, timeout=30) +# solr = pysolr.Solr('http://127.0.0.1:8983/solr/searchkey/', always_commit=True, timeout=30) + +class Keywords(models.Model): + _name = 'keywords' + _order= 'id desc' + + category_id = fields.Many2one('product.public.category', string='Category', required=True, help="Category to filter products when generating products for this keyword and to throw to solr") + keywords = fields.Char('Keywords', required=True) + product_ids = fields.Many2many( + 'product.product', + 'keywords_product_rel', + 'keyword_id', + 'product_id', + string='Products' + ) + name = fields.Char('Name', compute="_compute_name") + skip = fields.Boolean('Skip Generate Product', default=False, help="If checked, the system will not generate products for this keyword") + url = fields.Char('Website URL', compute="_compute_url", help="Generated website url based on keywords") + sum = fields.Integer('Total Product', compute="_compute_total_product", readonly=True, help="Total products found for this keyword including variants") + solr_flag = fields.Integer(string='Solr Flag', default=0, help="0=no sync needed, 2=needs sync, 1=queued") + + @api.depends('product_ids') + def _compute_total_product(self): + for record in self: + record.sum = len(record.product_ids) + + @api.depends('keywords') + def _compute_url(self): + prefix = "https://indoteknik.com/searchkey/" + for record in self: + if record.keywords: + slug = re.sub(r'[^a-zA-Z0-9]+', '-', record.keywords.strip().lower()) + slug = slug.strip('-') + record.url = prefix + slug + else: + record.url = False + + def _compute_name(self): + for record in self: + if not record.name: + record.name = record.keywords + + @api.constrains('keywords', 'category_id') + def check_already_exist(self): + model = self.env['keywords'] + for record in self: + match = model.search([ + ('keywords', '=', record.keywords), + ('category_id.id', '=', record.category_id.id), + ('id', '!=', record.id) + ]) + if match: + raise UserError("Tidak bisa create karena keywords sudah dipakai") + + def copy(self): + raise UserError("Duplicate Record not allowed") + + + def clear_products(self): + for record in self: + record.product_ids = [(5, 0, 0)] + + def generate_products(self): + for record in self: + if not record.keywords or record.skip: + continue + + keyword = f"%{record.keywords.strip()}%" + + # AND (pt.unpublished IS FALSE OR pt.unpublished IS NULL) + sql = """ + SELECT DISTINCT pp.id + FROM product_product pp + JOIN product_template pt ON pt.id = pp.product_tmpl_id + JOIN product_public_category_product_template_rel rel + ON rel.product_template_id = pt.id + WHERE + pt.product_rating >= 8 + AND pp.active IS TRUE + AND ( + pt.name ILIKE %s + OR pt.website_description ILIKE %s + ) + """ + + params = [ + keyword, + keyword, + ] + + if record.category_id: + child_categs = self.env['product.public.category'].search([ + ('id', 'child_of', record.category_id.id) + ]) + sql += " AND rel.product_public_category_id = ANY(%s)" + params.append(child_categs.ids) + + self.env.cr.execute(sql, params) + + rows = self.env.cr.fetchall() + product_ids = [r[0] for r in rows] + + if not product_ids: + raise UserError( + f"Tidak berhasil menemukan barang untuk keyword '{record.keywords}'" + ) + + record.with_context(skip_generate=True).write({ + 'product_ids': [(6, 0, product_ids)] + }) + + _logger.info( + "Product Found: Found %s products for keyword '%s'", + len(product_ids), + record.keywords + ) + + @api.onchange('keywords', 'category_id', 'product_ids') + def _onchange_solr_flag(self): + """Set solr_flag=2 when tracked fields change to trigger queue sync""" + for record in self: + if not record.skip and len(record.product_ids) > 0: + record.solr_flag = 2 + + def solr_flag_to_queue(self, limit=500): + """Find keywords with solr_flag=2 and create apache.solr.queue entries""" + keywords = self.search([('solr_flag', '=', 2)], limit=limit) + + for keyword in keywords: + # Create unique queue entry + queue_obj = self.env['apache.solr.queue'] + queue_obj.create_unique({ + 'res_model': 'keywords', + 'res_id': keyword.id, + 'function_name': '_sync_keywords_queue_callback' + }) + + # Set flag to indicate queued + keyword.solr_flag = 1 + + if keywords: + _logger.info( + 'Queued %s keywords for Solr synchronization', + len(keywords) + ) + + return True + + def _sync_keywords_queue_callback(self): + """Callback method executed by apache.solr.queue - syncs keyword data to Solr""" + documents = [] + for keyword in self: + # Skip syncing if product count is 0 + if len(keyword.product_ids) == 0: + _logger.info('Skipping Solr sync for keyword "%s" - no products found', keyword.keywords) + continue + + searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') + try: + doc = { + 'id': keyword.id, + 'category_id_i': keyword.category_id.id, + 'keywords_s': searchkey, + 'url_s': keyword.url, + 'product_ids_is': [p.product_tmpl_id.id for p in keyword.product_ids], + } + documents.append(doc) + except Exception as e: + _logger.error('failed %s', e) + _logger.error('doc data: %s', doc) + + if documents: + solr.add(documents) + + return True + + def sync_solr(self): + """Manual sync method for active_ids context (backward compatibility)""" + active_ids = self.env.context.get('active_ids', []) + if not active_ids: + _logger.warning("No active_ids found, nothing to sync") + return True + + keywords = self.browse(active_ids) + + documents = [] + for keyword in keywords: + # Skip syncing if product count is 0 + if len(keyword.product_ids) == 0: + _logger.info('Skipping Solr sync for keyword "%s" - no products found', keyword.keywords) + continue + + searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') + try: + doc = { + 'id': keyword.id, + 'category_id_i': keyword.category_id.id, + 'keywords_s': searchkey, + 'url_s': keyword.url, + 'product_ids_is': [p.product_tmpl_id.id for p in keyword.product_ids], + } + documents.append(doc) + except Exception as e: + _logger.error('failed %s', e) + _logger.error('doc data: %s', doc) + + if documents: + solr.add(documents) + + return True + + @api.model + def create(self, vals): + record = super().create(vals) + # self.check_already_exist() + # record.generate_products() + return record + + def write(self, vals): + result = super().write(vals) + + tracked_fields = ['keywords', 'category_id', 'product_ids'] + neded_sync = any(field in vals for field in tracked_fields) + if neded_sync and self.skip == False: + for record in self: + # Only flag for sync if there are products + if len(record.product_ids) > 0: + record.solr_flag = 2 + + return result diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 2c798f43..397bd06d 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -1,3 +1,4 @@ +from this import s from odoo import fields, models, api, tools, _ from datetime import datetime, timedelta, date from odoo.exceptions import UserError @@ -78,7 +79,7 @@ class ProductTemplate(models.Model): ('sp', 'Spare Part'), ('acc', 'Accessories') ], string='Kind of', copy=False) - sni = fields.Boolean(string='SNI') + sni = fields.Boolean(string='SNI') tkdn = fields.Boolean(string='TKDN') short_spesification = fields.Char(string='Short Spesification') merchandise_ok = fields.Boolean(string='Product Promotion') @@ -93,8 +94,9 @@ class ProductTemplate(models.Model): if self.env.user.id not in users_in_group.mapped('id') and active_model == None: raise UserError('Hanya MD yang bisa membuat Product') result = super(ProductTemplate, self).create(vals) + self.env['product.product']._add_product_to_keywords(result) return result - + # def write(self, values): # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) @@ -121,7 +123,7 @@ class ProductTemplate(models.Model): # qr_code_img = base64.b64encode(buffer.getvalue()).decode() # rec.qr_code = qr_code_img - + @api.constrains('name', 'internal_reference', 'x_manufacture') def required_public_categ_ids(self): for rec in self: @@ -134,7 +136,7 @@ class ProductTemplate(models.Model): def day_product_to_edit(self): day_products = [] - + for product in self: day_product = (product.write_date - product.create_date).days day_products.append(day_product) @@ -147,25 +149,25 @@ class ProductTemplate(models.Model): variants = product.product_variant_ids names = [x.name for x in variants] if variants else [product.name] default_codes = [x.default_code for x in variants] if variants else [product.default_code] - + domain = [ - ('default_code', '!=', False), + ('default_code', '!=', False), ('id', '!=', product.id), - '|', - ('name', 'in', names), + '|', + ('name', 'in', names), ('default_code', 'in', default_codes) ] - + product_exist = self.search(domain, limit=1) if len(product_exist) > 0: raise UserError('Name atau Internal Reference sudah digunakan pada produk lain') - + if self.env.user.is_purchasing_manager or self.env.user.is_editor_product or self.env.user.id in [1, 25]: continue - + if sum(product.day_product_to_edit()) > 0: raise UserError('Produk ini tidak dapat diubah') - + @api.constrains('name') def _validate_name(self): rule_regex = self.env['ir.config_parameter'].sudo().get_param('product.product.rule_name_regex') or '' @@ -174,7 +176,7 @@ class ProductTemplate(models.Model): pattern_suggest = rf"{rule_regex}" suggest = ''.join(re.findall(pattern_suggest, self.name)) raise UserError(f'Contoh yang benar adalah {suggest}') - + # def write(self, vals): # if 'solr_flag' not in vals and self.solr_flag == 1: # vals['solr_flag'] = 2 @@ -213,7 +215,7 @@ class ProductTemplate(models.Model): def unlink(self): if self._name == 'product.template': raise UserError('Maaf anda tidak bisa delete product') - + def update_new_product(self): current_time = datetime.now() delta_time = current_time - timedelta(days=30) @@ -244,7 +246,7 @@ class ProductTemplate(models.Model): for template in templates: if not template.default_code: template.default_code = 'IT.'+str(template.id) - + for variant in template.product_variant_ids: if not variant.default_code: variant.default_code = 'ITV.%s' % str(variant.id) @@ -336,7 +338,7 @@ class ProductTemplate(models.Model): def _get_stock_website(self): qty = self._get_stock_altama() print(qty) - + def get_stock_altama(self, item_code): current_time = datetime.now() current_time = current_time.strftime('%Y-%m-%d %H:%M:%S') @@ -347,7 +349,7 @@ class ProductTemplate(models.Model): token = token_data['access_token'] else: token = token_data.access_token - + url = "https://erpapi.altama.co.id/erp/api/stock/buffer/btob" auth = "Bearer "+token headers = { @@ -361,7 +363,7 @@ class ProductTemplate(models.Model): response = requests.post(url, headers=headers, json=json_data) if response.status_code != 200: return 0 - + datas = json.loads(response.text)['data'] qty = 0 for data in datas: @@ -380,12 +382,12 @@ class ProductTemplate(models.Model): data = { 'grant_type': 'client_credentials', } - + response = requests.post(url, headers=headers, data=data).json() lookup_json = json.dumps(response, indent=4, sort_keys=True) token = json.loads(lookup_json)['access_token'] expires_in = json.loads(lookup_json)['expires_in'] - + current_time = datetime.now() delta_time = current_time + timedelta(seconds=int(expires_in)) @@ -401,18 +403,18 @@ class ProductTemplate(models.Model): self.env['token.storage'].create([values]) return values - # ============================== + # ============================== def get_vendor_name(self, rec): """Get formatted name for vendor/supplier""" return rec.name.name if rec.name else f"ID {rec.id}" - + def get_attribute_line_name(self, rec): """Get formatted name for attribute line""" if rec.attribute_id and rec.value_ids: values = ", ".join(rec.value_ids.mapped('name')) return f"{rec.attribute_id.name}: {values}" return f"ID {rec.id}" - + def _get_vendor_field_label(self, field_name): """Get human-readable label for vendor fields""" field_labels = { @@ -428,10 +430,10 @@ class ProductTemplate(models.Model): 'date_end': 'End Date', 'min_qty': 'Quantity' } - return field_labels.get(field_name, field_name.replace('_', ' ').title()) + return field_labels.get(field_name, field_name.replace('_', ' ').title()) # ============================== - + def _collect_old_values(self, vals): """Collect old values before write""" return { @@ -442,7 +444,7 @@ class ProductTemplate(models.Model): } for record in self } - + def _prepare_attribute_line_info(self): """Prepare attribute line info for logging and update comparison""" line_info = {} @@ -455,7 +457,7 @@ class ProductTemplate(models.Model): 'value_names': ", ".join(line.value_ids.mapped('name')) } return line_info - + def _prepare_vendor_info(self): """Prepare vendor info for logging before they are deleted""" vendor_info = {} @@ -474,13 +476,13 @@ class ProductTemplate(models.Model): 'date_end': seller.date_end, } return vendor_info - + # ========================== - + def _get_context_with_all_info(self, vals): """Get context with all necessary info (attributes and vendors)""" context = dict(self.env.context) - + # Check for attribute line changes if 'attribute_line_ids' in vals: attribute_line_info = {} @@ -488,7 +490,7 @@ class ProductTemplate(models.Model): product_line_info = product._prepare_attribute_line_info() attribute_line_info.update(product_line_info) context['attribute_line_info'] = attribute_line_info - + # Check for vendor changes - store both for deletion and for comparing old values if 'seller_ids' in vals: vendor_info = {} @@ -497,18 +499,18 @@ class ProductTemplate(models.Model): # For deletion logging product_vendor_info = product._prepare_vendor_info() vendor_info.update(product_vendor_info) - + # For update comparison product_vendor_old = product._prepare_vendor_info() vendor_old_values.update(product_vendor_old) - + context['vendor_info'] = vendor_info context['vendor_old_values'] = vendor_old_values - + return context - + # ======================== - + def _log_image_changes(self, field_name, old_val, new_val): """Log image field changes""" label_map = { @@ -526,18 +528,18 @@ class ProductTemplate(models.Model): elif old_val != new_val: return f"<li><b>{label}</b>: image updated</li>" return None - + def _log_attribute_line_changes(self, commands): """Log changes to attribute lines with complete information""" # Get stored info from context stored_info = self.env.context.get('attribute_line_info', {}) - + for cmd in commands: if cmd[0] == 0: # Add new = self.env['product.template.attribute.line'].new(cmd[2]) attribute_name = new.attribute_id.name if new.attribute_id else 'Attribute' values = ", ".join(new.value_ids.mapped('name')) if new.value_ids else '' - + message = f"<b>Product Attribute</b>:<br/>{attribute_name} added<br/>" if values: message += f"Values: '{values}'" @@ -546,7 +548,7 @@ class ProductTemplate(models.Model): elif cmd[0] == 1: # Update rec_id = cmd[1] vals = cmd[2] - + # Get old values from context old_data = stored_info.get(rec_id, {}) if not old_data: @@ -561,10 +563,10 @@ class ProductTemplate(models.Model): 'value_ids': [(v.id, v.name) for v in rec.value_ids], 'value_names': ", ".join(rec.value_ids.mapped('name')) } - + changes = [] attribute_name = old_data.get('attribute_name', 'Attribute') - + # Check for attribute change if 'attribute_id' in vals: old_attr = old_data.get('attribute_name', '-') @@ -576,7 +578,7 @@ class ProductTemplate(models.Model): # Check for value changes if 'value_ids' in vals: old_vals = old_data.get('value_names', '') - + # Parse the command for value_ids new_value_ids = [] for value_cmd in vals['value_ids']: @@ -588,14 +590,14 @@ class ProductTemplate(models.Model): elif value_cmd[0] == 3: # Remove # This is more complex, would need current state pass - + # Get new value names if new_value_ids: new_values = self.env['product.attribute.value'].browse(new_value_ids) new_vals = ", ".join(new_values.mapped('name')) else: new_vals = "" - + if old_vals != new_vals: changes.append(f"Values: '{old_vals}' → '{new_vals}'") @@ -605,7 +607,7 @@ class ProductTemplate(models.Model): message += "<br/>".join(changes) self.message_post(body=message) - elif cmd[0] in (2, 3): # Remove + elif cmd[0] in (2, 3): # Remove # Use info from stored data line_data = stored_info.get(cmd[1]) if line_data: @@ -619,7 +621,7 @@ class ProductTemplate(models.Model): else: attribute_name = 'Attribute' values = f"ID {cmd[1]}" - + message = f"<b>Product Attribute</b>:<br/>{attribute_name} removed<br/>" if values: message += f"Values: '{values}'" @@ -627,25 +629,25 @@ class ProductTemplate(models.Model): elif cmd[0] == 5: # Clear all self.message_post(body=f"<b>Product Attribute</b>:<br/>All attributes removed") - + def _log_vendor_pricelist_changes(self, commands): """Log changes to vendor pricelist with complete information""" # Get stored info from context stored_info = self.env.context.get('vendor_info', {}) old_values_info = self.env.context.get('vendor_old_values', {}) - + for cmd in commands: if cmd[0] == 0: # Add vals = cmd[2] - + # Create temporary record to get proper display values temp_values = vals.copy() temp_values['product_tmpl_id'] = self.id new = self.env['product.supplierinfo'].new(temp_values) - + name = self.get_vendor_name(new) details = [] - + if 'price' in vals and vals['price'] is not None: details.append(f"<li>Price: {vals['price']}</li>") if 'min_qty' in vals and vals['min_qty'] is not None: @@ -662,18 +664,18 @@ class ProductTemplate(models.Model): if 'product_uom' in vals and vals['product_uom']: uom = self.env['uom.uom'].browse(vals['product_uom']) details.append(f"<li>Unit of Measure: {uom.name}</li>") - + if details: detail_str = f" with:<ul>{''.join(details)}</ul>" else: detail_str = "" - + self.message_post(body=f"<b>Vendor Pricelist</b>: added '{name}'{detail_str}") - + elif cmd[0] == 1: # Update rec_id = cmd[1] vals = cmd[2] - + # Get old values from context old_data = old_values_info.get(rec_id, {}) if not old_data: @@ -694,10 +696,10 @@ class ProductTemplate(models.Model): 'date_start': rec.date_start, 'date_end': rec.date_end, } - + name = old_data.get('name', f'ID {rec_id}') changes = [] - + # Check each field in vals for changes for field, new_value in vals.items(): if field == 'name': @@ -707,9 +709,9 @@ class ProductTemplate(models.Model): new_name = self.env['res.partner'].browse(new_value).name if new_value else 'None' changes.append(f"<li>Vendor: {old_name} → {new_name}</li>") continue - + old_value = old_data.get(field) - + # Format values based on field type if field == 'currency_id': if old_value != new_value: @@ -741,14 +743,14 @@ class ProductTemplate(models.Model): # Compare numeric values properly old_num = float(old_value) if old_value is not None else 0.0 new_num = float(new_value) if new_value is not None else 0.0 - + if field == 'delay': # Integer field old_num = int(old_num) new_num = int(new_num) - + if old_num == new_num: continue - + old_str = str(old_value) if old_value is not None else 'None' new_str = str(new_value) if new_value is not None else 'None' else: @@ -757,20 +759,20 @@ class ProductTemplate(models.Model): continue old_str = str(old_value) if old_value is not None else 'None' new_str = str(new_value) if new_value is not None else 'None' - + label = self._get_vendor_field_label(field) changes.append(f"<li>{label}: {old_str} → {new_str}</li>") - + if changes: changes_str = f"<ul>{''.join(changes)}</ul>" self.message_post(body=f"<b>Vendor Pricelist</b>: updated '{name}':{changes_str}") - + elif cmd[0] in (2, 3): # Remove vendor_data = stored_info.get(cmd[1]) if vendor_data: name = vendor_data['name'] details = [] - + if vendor_data.get('price'): details.append(f"<li>Price: {vendor_data['price']}</li>") if vendor_data.get('min_qty'): @@ -779,7 +781,7 @@ class ProductTemplate(models.Model): details.append(f"<li>Product Name: {vendor_data['product_name']}</li>") if vendor_data.get('delay'): details.append(f"<li>Delivery Lead Time: {vendor_data['delay']}</li>") - + if details: detail_str = f"<ul>{''.join(details)}</ul>" else: @@ -795,7 +797,7 @@ class ProductTemplate(models.Model): details.append(f"<li>Quantity: {rec.min_qty}</li>") if rec.product_name: details.append(f"<li>Product Name: {rec.product_name}</li>") - + if details: detail_str = f"<ul>{''.join(details)}</ul>" else: @@ -803,12 +805,12 @@ class ProductTemplate(models.Model): else: name = f"ID {cmd[1]}" detail_str = "" - + self.message_post(body=f"<b>Vendor Pricelist</b>: removed '{name}'{detail_str}") - + elif cmd[0] == 5: # Clear all self.message_post(body=f"<b>Vendor Pricelist</b>: all removed") - + def _log_field_changes_product(self, vals, old_values): """Revised - Log general field changes for product template without posting to variants""" exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] @@ -892,6 +894,12 @@ class ProductTemplate(models.Model): result = super().write(vals) # Log changes self._log_field_changes_product(vals, old_values) + + # Add product to keywords + keyword_trigger = ['name', 'website_description', 'unpublished'] + if any(field in vals for field in keyword_trigger): + for product in self: + self.env['product.product']._add_product_to_keywords(product) return result # def write(self, vals): @@ -900,7 +908,7 @@ class ProductTemplate(models.Model): # # raise UserError('Tidak dapat mengubah produk sementara') # self._log_field_changes(vals) # return super(ProductTemplate, self).write(vals) - + class ProductProduct(models.Model): _inherit = "product.product" web_price = fields.Float( @@ -938,6 +946,25 @@ class ProductProduct(models.Model): qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') qty_pcs_box = fields.Float("Pcs Box") barcode_box = fields.Char("Barcode Box") + keyword_id = fields.Many2one('keywords', string='Keyword') + + def _add_product_to_keywords(self,product): + keywords_model = self.env['keywords'] + if not product: + return False + + for product in self: + match_keywords = keywords_model.search([ + '|', + ('name', 'ilike', product.name), + ('keywords', 'ilike', product.website_description) + ]) + + for kw in match_keywords.filtered(lambda k: not k.skip): + if not self.unpublished and product.id not in kw.product_ids.ids: + kw.write({'product_ids': [(4, product.id)]}) + + return True has_magento = fields.Boolean(string='Has Magento?', default=False, readonly=True) @@ -961,8 +988,9 @@ class ProductProduct(models.Model): if self.env.user.id not in users_in_group.mapped('id') and active_model == None: raise UserError('Hanya MD yang bisa membuat Product') result = super(ProductProduct, self).create(vals) + self._add_product_to_keywords(result) return result - + # def write(self, values): # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id # active_model = self.env.context.get('active_model') @@ -978,7 +1006,7 @@ class ProductProduct(models.Model): if not rec.active: rec.qr_code_variant = False # Clear the QR Code for archived variants continue - + qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, @@ -1066,11 +1094,11 @@ class ProductProduct(models.Model): if self.qty_available_bandengan < qty_purchase: return 'harus beli' return 'masih cukup' - + def _get_qty_upcoming(self): for product in self: product.qty_upcoming = product.incoming_qty + product.qty_available - + def _get_qty_sold(self): for product in self: order_line = self.env['sale.order.line'].search([ @@ -1081,13 +1109,13 @@ class ProductProduct(models.Model): def day_product_to_edit(self): day_products = [] - + for product in self: day_product = (product.write_date - product.create_date).days day_products.append(day_product) return day_products - + @api.constrains('name') def _validate_name(self): rule_regex = self.env['ir.config_parameter'].sudo().get_param('product.product.rule_name_regex') or '' @@ -1096,7 +1124,7 @@ class ProductProduct(models.Model): pattern_suggest = rf"{rule_regex}" suggest = ''.join(re.findall(pattern_suggest, self.name)) raise UserError(f'Contoh yang benar adalah {suggest}') - + def _get_qty_incoming_bandengan(self): for product in self: qty = self.env['v.move.outstanding'].read_group( @@ -1218,11 +1246,11 @@ class ProductProduct(models.Model): for product in self: stock_vendor = self.env['stock.vendor'].search([('product_variant_id', '=', product.id)], limit=1) product.qty_stock_vendor = stock_vendor.quantity + product.qty_available - + def unlink(self): if self._name == 'product.product': raise UserError('Maaf anda tidak bisa delete product') - + def _get_active_flash_sale(self): current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') pricelist = self.env['product.pricelist'].search([ @@ -1248,7 +1276,7 @@ class ProductProduct(models.Model): def _log_field_changes_product_variants(self, vals, old_values): """Revised - Log field changes for variants without posting to template""" exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] - + # Custom labels for image fields custom_labels = { 'image_1920': 'Main Image', @@ -1325,7 +1353,11 @@ class ProductProduct(models.Model): 'public_categ_ids', 'search_rank', 'search_rank_weekly', 'image_1920', 'unpublished', 'image_carousel_lines' ] - + # pake ini kalau mau Cek semua field + # if vals: + trigger_fields = ['name', 'website_description', 'unpublished'] + if any(f in vals for f in trigger_fields): + self._add_product_to_keywords(vals) if any(field in vals for field in tracked_fields): old_values = self._collect_old_values(vals) result = super().write(vals) @@ -1354,7 +1386,7 @@ class OutstandingMove(models.Model): tools.drop_view_if_exists(self.env.cr, self._table) self.env.cr.execute(""" CREATE OR REPLACE VIEW %s AS - select sm.id, sm.reference, sm.product_id, + select sm.id, sm.reference, sm.product_id, sm.product_uom_qty as qty_need, sm.location_id, sm.location_dest_id, sm.raw_material_production_id as mo_id, diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 1294970c..b6583ed5 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -216,4 +216,5 @@ access_surat_piutang_user,surat.piutang user,model_surat_piutang,,1,1,1,1 access_surat_piutang_line_user,surat.piutang.line user,model_surat_piutang_line,,1,1,1,1 access_sj_tele,access.sj.tele,model_sj_tele,base.group_system,1,1,1,1 access_stock_picking_sj_document,stock.picking.sj.document,model_stock_picking_sj_document,base.group_user,1,1,1,1 -access_update_depreciation_move_wizard,access.update.depreciation.move.wizard,model_update_depreciation_move_wizard,,1,1,1,1
\ No newline at end of file +access_update_depreciation_move_wizard,access.update.depreciation.move.wizard,model_update_depreciation_move_wizard,,1,1,1,1 +access_keywords,keywords,model_keywords,base.group_user,1,1,1,1 diff --git a/indoteknik_custom/views/find_page.xml b/indoteknik_custom/views/find_page.xml index c752aa98..fc9bddbb 100644 --- a/indoteknik_custom/views/find_page.xml +++ b/indoteknik_custom/views/find_page.xml @@ -25,7 +25,7 @@ <group> <field name="category_id"/> <field name="brand_id"/> - <field name="url"/> + <field name="url" /> </group> <group> <field name="create_uid"/> @@ -62,9 +62,18 @@ <field name="view_mode">tree,form</field> </record> + + <record id="ir_actions_server_find_page_sync_to_solr" model="ir.actions.server"> + <field name="name">Sync to solr</field> + <field name="model_id" ref="indoteknik_custom.model_web_find_page"/> + <field name="binding_model_id" ref="indoteknik_custom.model_web_find_page"/> + <field name="state">code</field> + <field name="code">model._sync_to_solr()</field> + </record> + <menuitem id="menu_web_find_page" name="Web Find Page" action="web_find_page_action" parent="website_sale.menu_orders" sequence="8"/> -</odoo>
\ No newline at end of file +</odoo> diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml new file mode 100644 index 00000000..a33edae0 --- /dev/null +++ b/indoteknik_custom/views/keywords.xml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <record id="keywords_tree" model="ir.ui.view"> + <field name="name">keywords.tree</field> + <field name="model">keywords</field> + <field name="arch" type="xml"> + <tree > + <field name="category_id" /> + <field name="keywords" /> + <field name="url" /> + <field name="sum" /> + <field name="solr_flag" readonly="1"/> + <field name="product_ids" widget="many2many_tags" /> + </tree> + </field> + </record> + + <record id="keywords_form" model="ir.ui.view"> + <field name="name">keywords.form</field> + <field name="model">keywords</field> + <field name="arch" type="xml"> + <form> + <header> + <button name="generate_products" string="Generate Product Manual" type="object" class="oe_highlight"/> + <button name="clear_products" string="Clear Generated Products" type="object" /> + </header> + <sheet> + <div class="oe_title"> + <h1> + <field name="name" readonly="1" class="oe_inline"/> + </h1> + </div> + <group> + <field name="category_id" /> + <field name="keywords" /> + <field name="url" /> + <field name="sum" /> + <field name="product_ids" widget="many2many_tags" /> + <field name="skip" /> + <field name="solr_flag" readonly="1"/> + </group> + </sheet> + </form> + </field> + </record> + + <record id="view_keywords_filter" model="ir.ui.view"> + <field name="name">keywords.list.select</field> + <field name="model">keywords</field> + <field name="priority" eval="15"/> + <field name="arch" type="xml"> + <search string="Search Keywords"> + <field name="category_id"/> + <field name="keywords"/> + <field name="sum"/> + <field name="solr_flag" readonly="1"/> + <field name="product_ids" widget="many2many_tags"/> + </search> + </field> + </record> + <record id="action_keywords" model="ir.actions.act_window"> + <field name="name">Keywords</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">keywords</field> + <field name="search_view_id" ref="view_keywords_filter"/> + <field name="view_mode">tree,form</field> + </record> + + <record id="ir_actions_server_keywords_sync_to_solr" model="ir.actions.server"> + <field name="name">Sync to solr</field> + <field name="model_id" ref="indoteknik_custom.model_keywords"/> + <field name="binding_model_id" ref="indoteknik_custom.model_keywords"/> + <field name="state">code</field> + <field name="code">model.sync_solr()</field> + </record> + <menuitem id="menu_keywords" + name="Keywords" + parent="website_sale.menu_orders" + action="action_keywords" + sequence="100"/> + + <data noupdate="1"> + <record id="cron_keywords_solr_flag_queue" model="ir.cron"> + <field name="name">Sync Keywords To Solr: Queue Solr Flag 2</field> + <field name="interval_number">1</field> + <field name="interval_type">hours</field> + <field name="numbercall">-1</field> + <field name="doall" eval="False"/> + <field name="model_id" ref="model_keywords"/> + <field name="code">model.solr_flag_to_queue(limit=500)</field> + <field name="state">code</field> + <field name="priority">55</field> + <field name="active">False</field> + </record> + </data> +</odoo> |
