summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xindoteknik_custom/__manifest__.py9
-rwxr-xr-xindoteknik_custom/models/__init__.py3
-rw-r--r--indoteknik_custom/models/keywords.py232
-rwxr-xr-xindoteknik_custom/models/product_template.py202
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv3
-rw-r--r--indoteknik_custom/views/find_page.xml13
-rw-r--r--indoteknik_custom/views/keywords.xml96
7 files changed, 463 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..4ab649dc
--- /dev/null
+++ b/indoteknik_custom/models/keywords.py
@@ -0,0 +1,232 @@
+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:
+ 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:
+ 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:
+ 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:
+ 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 bc290370..4ffefc68 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -215,4 +215,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>