summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIndoteknik . <it@fixcomart.co.id>2025-05-10 11:59:11 +0700
committerIndoteknik . <it@fixcomart.co.id>2025-05-10 11:59:11 +0700
commit0362205a2ac1404fffccdd02691f121a4ffac76e (patch)
tree86bf28e29332137cf566d7c10f3942e727ea9538
parent474755658e732235a7855810b993184c23f75386 (diff)
(andri) perapihan log note product di bagian vendor dan product attribute serta penyesuaian yang lain
-rwxr-xr-xindoteknik_custom/models/product_template.py476
1 files changed, 439 insertions, 37 deletions
diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py
index e95060e6..19de8bb7 100755
--- a/indoteknik_custom/models/product_template.py
+++ b/indoteknik_custom/models/product_template.py
@@ -388,8 +388,39 @@ class ProductTemplate(models.Model):
self.env['token.storage'].create([values])
return values
- # simpan data lama
+ # ==============================
+ 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 = {
+ 'name': 'Vendor',
+ 'currency_id': 'Currency',
+ 'product_uom': 'Unit of Measure',
+ 'price': 'Price',
+ 'delay': 'Delivery Lead Time',
+ 'product_id': 'Product Variant',
+ 'product_name': 'Vendor Product Name',
+ 'product_code': 'Vendor Product Code',
+ 'date_start': 'Start Date',
+ 'date_end': 'End Date',
+ 'min_qty': 'Quantity'
+ }
+ return field_labels.get(field_name, field_name.replace('_', ' ').title())
+
+ # ==============================
+
def _collect_old_values(self, vals):
+ """Collect old values before write"""
return {
record.id: {
field_name: record[field_name]
@@ -397,37 +428,417 @@ class ProductTemplate(models.Model):
if field_name in record._fields
}
for record in self
- }
-
- # log perubahan field
- def _log_field_changes(self, vals, old_values):
- exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited']
- custom_labels = {
+ }
+
+ def _prepare_attribute_line_info(self):
+ """Prepare attribute line info for logging and update comparison"""
+ line_info = {}
+ for line in self.attribute_line_ids:
+ line_info[line.id] = {
+ 'name': self.get_attribute_line_name(line),
+ 'attribute_id': line.attribute_id.id if line.attribute_id else None,
+ 'attribute_name': line.attribute_id.name if line.attribute_id else None,
+ 'value_ids': [(v.id, v.name) for v in line.value_ids],
+ '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 = {}
+ for seller in self.seller_ids:
+ vendor_info[seller.id] = {
+ 'name': self.get_vendor_name(seller),
+ 'price': seller.price,
+ 'min_qty': seller.min_qty,
+ 'delay': seller.delay,
+ 'product_name': seller.product_name,
+ 'product_code': seller.product_code,
+ 'currency_id': seller.currency_id.id if seller.currency_id else None,
+ 'product_uom': seller.product_uom.id if seller.product_uom else None,
+ 'product_id': seller.product_id.id if seller.product_id else None,
+ 'date_start': seller.date_start,
+ '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 = {}
+ for product in self:
+ 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 = {}
+ vendor_old_values = {}
+ for product in self:
+ # 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 = {
'image_1920': 'Main Image',
'image_carousel_lines': 'Carousel Images',
'product_template_image_ids': 'Extra Product Media',
- 'seller_ids': 'Vendor Pricelist',
- 'attribute_line_ids': 'Product Attributes',
}
+ label = label_map.get(field_name, field_name)
+
+ if old_val == 'None' and new_val != 'None':
+ return f"<li><b>{label}</b>: image added</li>"
+ elif old_val != 'None' and new_val == 'None':
+ return f"<li><b>{label}</b>: image removed</li>"
+ 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}'"
+ self.message_post(body=message)
+
+ 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:
+ # Fallback: get current record
+ rec = self.env['product.template.attribute.line'].browse(rec_id)
+ if not rec.exists():
+ continue
+ old_data = {
+ 'name': self.get_attribute_line_name(rec),
+ 'attribute_id': rec.attribute_id.id if rec.attribute_id else None,
+ 'attribute_name': rec.attribute_id.name if rec.attribute_id else None,
+ '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', '-')
+ new_attr = self.env['product.attribute'].browse(vals['attribute_id']).name
+ if old_attr != new_attr:
+ attribute_name = new_attr # Update attribute name for display
+ changes.append(f"Attribute changed from '{old_attr}' to '{new_attr}'")
+
+ # 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']:
+ if isinstance(value_cmd, (list, tuple)):
+ if value_cmd[0] == 6: # Replace all
+ new_value_ids = value_cmd[2]
+ elif value_cmd[0] == 4: # Add
+ new_value_ids.append(value_cmd[1])
+ 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}'")
+
+ if changes:
+ # Format with attribute name
+ message = f"<b>Product Attribute</b>:<br/>{attribute_name} updated<br/>"
+ message += "<br/>".join(changes)
+ self.message_post(body=message)
+
+ elif cmd[0] in (2, 3): # Remove
+ # Use info from stored data
+ line_data = stored_info.get(cmd[1])
+ if line_data:
+ attribute_name = line_data.get('attribute_name', 'Attribute')
+ values = line_data.get('value_names', '')
+ else:
+ rec = self.env['product.template.attribute.line'].browse(cmd[1])
+ if rec.exists():
+ attribute_name = rec.attribute_id.name if rec.attribute_id else 'Attribute'
+ values = ", ".join(rec.value_ids.mapped('name')) if rec.value_ids else ''
+ 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}'"
+ self.message_post(body=message)
+
+ 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:
+ details.append(f"<li>Quantity: {vals['min_qty']}</li>")
+ if 'delay' in vals and vals['delay'] is not None:
+ details.append(f"<li>Delivery Lead Time: {vals['delay']}</li>")
+ if 'product_name' in vals and vals['product_name']:
+ details.append(f"<li>Vendor Product Name: {vals['product_name']}</li>")
+ if 'product_code' in vals and vals['product_code']:
+ details.append(f"<li>Vendor Product Code: {vals['product_code']}</li>")
+ if 'currency_id' in vals and vals['currency_id']:
+ currency = self.env['res.currency'].browse(vals['currency_id'])
+ details.append(f"<li>Currency: {currency.name}</li>")
+ 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:
+ # Fallback: get current record
+ rec = self.env['product.supplierinfo'].browse(rec_id)
+ if not rec.exists():
+ continue
+ old_data = {
+ 'name': self.get_vendor_name(rec),
+ 'price': rec.price,
+ 'min_qty': rec.min_qty,
+ 'delay': rec.delay,
+ 'product_name': rec.product_name,
+ 'product_code': rec.product_code,
+ 'currency_id': rec.currency_id.id if rec.currency_id else None,
+ 'product_uom': rec.product_uom.id if rec.product_uom else None,
+ 'product_id': rec.product_id.id if rec.product_id else None,
+ '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':
+ # Special handling for vendor name change
+ if new_value != old_data.get('name'):
+ old_name = old_data.get('name', 'None')
+ 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:
+ old_str = self.env['res.currency'].browse(old_value).name if old_value else 'None'
+ new_str = self.env['res.currency'].browse(new_value).name if new_value else 'None'
+ else:
+ continue
+ elif field == 'product_uom':
+ if old_value != new_value:
+ old_str = self.env['uom.uom'].browse(old_value).name if old_value else 'None'
+ new_str = self.env['uom.uom'].browse(new_value).name if new_value else 'None'
+ else:
+ continue
+ elif field == 'product_id':
+ if old_value != new_value:
+ old_str = self.env['product.product'].browse(old_value).display_name if old_value else 'None'
+ new_str = self.env['product.product'].browse(new_value).display_name if new_value else 'None'
+ else:
+ continue
+ elif field in ['date_start', 'date_end']:
+ if str(old_value) != str(new_value):
+ old_str = old_value.strftime('%Y-%m-%d') if old_value else 'None'
+ new_str = new_value if new_value else 'None'
+ else:
+ continue
+ else:
+ # For numeric and other fields
+ if field in ['price', 'min_qty', 'delay']:
+ # 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:
+ # String and other types
+ if str(old_value) == str(new_value):
+ 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'):
+ details.append(f"<li>Quantity: {vendor_data['min_qty']}</li>")
+ if vendor_data.get('product_name'):
+ 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:
+ detail_str = ""
+ else:
+ rec = self.env['product.supplierinfo'].browse(cmd[1])
+ if rec.exists():
+ name = self.get_vendor_name(rec)
+ details = []
+ if rec.price:
+ details.append(f"<li>Price: {rec.price}</li>")
+ if rec.min_qty:
+ 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:
+ detail_str = ""
+ 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):
+ """Log general field changes for product template"""
+ exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited']
+ image_fields = ['image_1920', 'image_carousel_lines', 'product_template_image_ids']
+
for record in self:
changes = []
+
for field_name in vals:
if field_name not in record._fields or field_name in exclude_fields:
continue
field = record._fields[field_name]
- field_label = custom_labels.get(field_name, field.string or field_name)
- old_value = old_values.get(record.id, {}).get(field_name)
- new_value = record[field_name] # nilai setelah write
- def stringify(val, field, record):
+ # Handle image fields specially
+ if field_name in image_fields:
+ old_val = 'None' if not old_values.get(record.id, {}).get(field_name) else 'Yes'
+ new_val = 'None' if not record[field_name] else 'Yes'
+ image_msg = record._log_image_changes(field_name, old_val, new_val)
+ if image_msg:
+ changes.append(image_msg)
+ continue
+
+ # Handle vendor fields
+ if field_name == 'seller_ids':
+ commands = vals[field_name]
+ if isinstance(commands, list):
+ record._log_vendor_pricelist_changes(commands)
+ continue
+
+ # Handle attribute lines
+ if field_name == 'attribute_line_ids':
+ commands = vals[field_name]
+ if isinstance(commands, list):
+ record._log_attribute_line_changes(commands)
+ continue
+
+ # Handle other fields
+ def stringify(val):
if val in [None, False]:
return 'None'
if isinstance(field, fields.Selection):
- selection = field.selection
- if callable(selection):
- selection = selection(record)
+ selection = field.selection(record) if callable(field.selection) else field.selection
return dict(selection).get(val, str(val))
if isinstance(field, fields.Boolean):
return 'Yes' if val else 'No'
@@ -446,36 +857,27 @@ class ProductTemplate(models.Model):
if hasattr(records[0], attr):
return ", ".join(records.mapped(attr))
return ", ".join(str(r.id) for r in records)
- if isinstance(field, fields.One2many):
- records = val if isinstance(val, models.BaseModel) else record[field.name]
- if not records:
- return 'None'
- return f"{field.comodel_name}({', '.join(str(r.id) for r in records)})"
return str(val)
- old_val_str = stringify(old_value, field, record)
- new_val_str = stringify(new_value, field, record)
+ old_val_str = stringify(old_values.get(record.id, {}).get(field_name))
+ new_val_str = stringify(record[field_name])
if old_val_str != new_val_str:
- if field_name in custom_labels:
- if old_val_str == 'None' and new_val_str != 'None':
- changes.append(f"<li><b>{field_label}</b>: added</li>")
- elif old_val_str != 'None' and new_val_str == 'None':
- changes.append(f"<li><b>{field_label}</b>: removed</li>")
- else:
- changes.append(f"<li><b>{field_label}</b>: updated</li>")
- else:
- changes.append(f"<li><b>{field_label}</b>: '{old_val_str}' → '{new_val_str}'</li>")
+ field_label = field.string or field_name
+ changes.append(f"<li><b>{field_label}</b>: '{old_val_str}' → '{new_val_str}'</li>")
if changes:
- message = "<b>Updated:</b><ul>%s</ul>" % "".join(changes)
- record.message_post(body=message)
+ record.message_post(body=f"<b>Updated:</b><ul>{''.join(changes)}</ul>")
# simpan data lama dan log perubahan field
def write(self, vals):
+ context = self._get_context_with_all_info(vals)
+ if context != self.env.context:
+ self = self.with_context(**context)
old_values = self._collect_old_values(vals)
result = super().write(vals)
- self._log_field_changes(vals, old_values)
+ # Log changes
+ self._log_field_changes_product(vals, old_values)
return result
# def write(self, vals):
@@ -814,7 +1216,7 @@ class ProductProduct(models.Model):
}
# log perubahan field
- def _log_field_changes(self, vals, old_values):
+ def _log_field_changes_product_variants(self, vals, old_values):
exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited']
# for image fields, use custom labels
custom_labels = {
@@ -888,7 +1290,7 @@ class ProductProduct(models.Model):
def write(self, vals):
old_values = self._collect_old_values(vals)
result = super().write(vals)
- self._log_field_changes(vals, old_values)
+ self._log_field_changes_product_variants(vals, old_values)
return result
class OutstandingMove(models.Model):