From 8923005630ca26e9863f71c031a28183eb590727 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 7 May 2025 16:55:43 +0700 Subject: (andri) setiap melakukan edit product, history perubahan akan tercatat di log note --- indoteknik_custom/models/product_template.py | 75 ++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index a09570f4..8e475f95 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -388,12 +388,77 @@ class ProductTemplate(models.Model): self.env['token.storage'].create([values]) return values + # simpan data lama + def _collect_old_values(self, vals): + return { + record.id: { + field_name: record[field_name] + for field_name in vals.keys() + 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'] + + 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 = 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): + if val in [None, False]: + return 'None' + if isinstance(field, fields.Selection): + return dict(field.selection).get(val, str(val)) + if isinstance(field, fields.Boolean): + return 'Yes' if val else 'No' + if isinstance(field, fields.Many2one): + return val.name if hasattr(val, 'name') else str(val) + if isinstance(field, fields.Many2many): + records = val if isinstance(val, models.Model) else val + names = [] + if records: + for attr in ['name', 'x_name', 'display_name']: + if hasattr(records[0], attr): + names = records.mapped(attr) + break + if not names: + names = [str(r.id) for r in records] + return ", ".join(names) if names else 'None' + return str(val) + + old_val_str = stringify(old_value) + new_val_str = stringify(new_value) + + if old_val_str != new_val_str: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + + if changes: + message = "Updated:" % "".join(changes) + record.message_post(body=message) + + # simpan data lama dan log perubahan field def write(self, vals): - # for rec in self: - # if rec.id == 224484: - # raise UserError('Tidak dapat mengubah produk sementara') - - return super(ProductTemplate, self).write(vals) + old_values = self._collect_old_values(vals) + result = super().write(vals) + self._log_field_changes(vals, old_values) + return result + + # def write(self, vals): + # # for rec in self: + # # if rec.id == 224484: + # # 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" -- cgit v1.2.3 From 75bc2e5226862b8401424942ba464f6e70a03604 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 8 May 2025 09:41:15 +0700 Subject: (andri) add log note update field pada product variant --- indoteknik_custom/models/product_template.py | 83 +++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 8e475f95..99ae73d3 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -418,7 +418,10 @@ class ProductTemplate(models.Model): if val in [None, False]: return 'None' if isinstance(field, fields.Selection): - return dict(field.selection).get(val, str(val)) + selection = field.selection + if callable(selection): + selection = selection(record) + return dict(selection).get(val, str(val)) if isinstance(field, fields.Boolean): return 'Yes' if val else 'No' if isinstance(field, fields.Many2one): @@ -777,6 +780,84 @@ class ProductProduct(models.Model): ], limit=1) return pricelist + # simpan data lama + def _collect_old_values(self, vals): + return { + record.id: { + field_name: record[field_name] + for field_name in vals.keys() + 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'] + + 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 = field.string or field_name + old_value = old_values.get(record.id, {}).get(field_name) + new_value = record[field_name] + + def stringify(val): + if val in [None, False]: + return 'None' + + if isinstance(field, fields.Selection): + selection = field.selection + if callable(selection): + selection = selection(record) + return dict(selection).get(val, str(val)) + + if isinstance(field, fields.Boolean): + return 'Yes' if val else 'No' + + if isinstance(field, fields.Many2one): + if isinstance(val, int): + rec = self.env[field.comodel_name].browse(val) + return rec.display_name if rec.exists() else str(val) + elif isinstance(val, models.BaseModel): + return val.display_name + return str(val) + + if isinstance(field, fields.Many2many): + records = record[field_name] + names = [] + if records: + for attr in ['name', 'x_name', 'display_name']: + if hasattr(records[0], attr): + names = records.mapped(attr) + break + if not names: + names = [str(r.id) for r in records] + return ", ".join(names) if names else 'None' + + return str(val) + + + old_val_str = stringify(old_value) + new_val_str = stringify(new_value) + + if old_val_str != new_val_str: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + + if changes: + message = "Updated:" % "".join(changes) + record.message_post(body=message) + + # simpan data lama dan log perubahan field + def write(self, vals): + old_values = self._collect_old_values(vals) + result = super().write(vals) + self._log_field_changes(vals, old_values) + return result class OutstandingMove(models.Model): _name = 'v.move.outstanding' -- cgit v1.2.3 From 35ed7159d12a5eb915f4c689535e2e8654b43e63 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 8 May 2025 17:09:56 +0700 Subject: (andri) add inherit mail.thread & mail.activity.mixim untuk log note pada pricelist --- indoteknik_custom/models/product_pricelist.py | 8 +++++--- indoteknik_custom/views/product_pricelist.xml | 6 ++++++ indoteknik_custom/views/product_pricelist_item.xml | 6 ++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/product_pricelist.py b/indoteknik_custom/models/product_pricelist.py index ea3ee6cf..46d12f4d 100644 --- a/indoteknik_custom/models/product_pricelist.py +++ b/indoteknik_custom/models/product_pricelist.py @@ -3,8 +3,9 @@ from datetime import datetime, timedelta class ProductPricelist(models.Model): - _inherit = 'product.pricelist' - + _name = 'product.pricelist' + _inherit = ['product.pricelist', 'mail.thread', 'mail.activity.mixin'] + is_flash_sale = fields.Boolean(string='Flash Sale', default=False) is_show_program = fields.Boolean(string='Show Program', default=False) banner = fields.Binary(string='Banner') @@ -56,7 +57,8 @@ class ProductPricelist(models.Model): return tier_name class ProductPricelistItem(models.Model): - _inherit = 'product.pricelist.item' + _name = 'product.pricelist.item' + _inherit = ['product.pricelist.item', 'mail.thread', 'mail.activity.mixin'] manufacture_id = fields.Many2one('x_manufactures', string='Manufacture') computed_price = fields.Float(string='Computed Price') \ No newline at end of file diff --git a/indoteknik_custom/views/product_pricelist.xml b/indoteknik_custom/views/product_pricelist.xml index 3c2b8b8d..6a1111cf 100644 --- a/indoteknik_custom/views/product_pricelist.xml +++ b/indoteknik_custom/views/product_pricelist.xml @@ -25,6 +25,12 @@ + +
    + + +
    +
    diff --git a/indoteknik_custom/views/product_pricelist_item.xml b/indoteknik_custom/views/product_pricelist_item.xml index 973ae181..86ee9389 100755 --- a/indoteknik_custom/views/product_pricelist_item.xml +++ b/indoteknik_custom/views/product_pricelist_item.xml @@ -11,6 +11,12 @@ product.pricelist.item + +
    + + +
    +
    -- cgit v1.2.3 From aca89f41cf980e2ea9b7f266d4792be52d2e4aa1 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Thu, 8 May 2025 22:12:07 +0700 Subject: (andri) add log note pada purchase list untuk catat perubahan tiap fields --- indoteknik_custom/models/purchase_pricelist.py | 67 +++++++++++++++++++++++++- indoteknik_custom/views/purchase_pricelist.xml | 7 +++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py index e5b35d7f..6ac74c69 100755 --- a/indoteknik_custom/models/purchase_pricelist.py +++ b/indoteknik_custom/models/purchase_pricelist.py @@ -6,6 +6,7 @@ from pytz import timezone class PurchasePricelist(models.Model): _name = 'purchase.pricelist' _rec_name = 'product_id' + _inherit = ['mail.thread', 'mail.activity.mixin'] name = fields.Char(string='Name', compute="_compute_name") product_id = fields.Many2one('product.product', string="Product", required=True) @@ -120,4 +121,68 @@ class PurchasePricelist(models.Model): rec.sync_pricelist_tier() rec.product_id.product_tmpl_id._create_solr_queue('_sync_price_to_solr') - \ No newline at end of file + + def _collect_old_values(self, vals): + return { + record.id: { + field_name: record[field_name] + for field_name in vals.keys() + if field_name in record._fields + } + for record in self + } + + def _log_field_changes(self, vals, old_values): + exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] + + 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 = field.string or field_name + old_value = old_values.get(record.id, {}).get(field_name) + new_value = record[field_name] + + def stringify(val): + if val in [None, False]: + return 'None' + if isinstance(field, fields.Selection): + selection = field.selection + if callable(selection): + selection = selection(record) + return dict(selection).get(val, str(val)) + if isinstance(field, fields.Boolean): + return 'Yes' if val else 'No' + if isinstance(field, fields.Many2one): + return val.name if hasattr(val, 'name') else str(val) + if isinstance(field, fields.Many2many): + records = val if isinstance(val, models.Model) else val + names = [] + if records: + for attr in ['name', 'x_name', 'display_name']: + if hasattr(records[0], attr): + names = records.mapped(attr) + break + if not names: + names = [str(r.id) for r in records] + return ", ".join(names) if names else 'None' + return str(val) + + old_val_str = stringify(old_value) + new_val_str = stringify(new_value) + + if old_val_str != new_val_str: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + + if changes: + message = "Updated:
      %s
    " % "".join(changes) + record.message_post(body=message) + + def write(self, vals): + old_values = self._collect_old_values(vals) + result = super(PurchasePricelist, self).write(vals) + self._log_field_changes(vals, old_values) + return result \ No newline at end of file diff --git a/indoteknik_custom/views/purchase_pricelist.xml b/indoteknik_custom/views/purchase_pricelist.xml index ca5cd416..409e3b6a 100755 --- a/indoteknik_custom/views/purchase_pricelist.xml +++ b/indoteknik_custom/views/purchase_pricelist.xml @@ -20,6 +20,8 @@ + + @@ -52,6 +54,11 @@ +
    + + + +
    -- cgit v1.2.3 From efba26bf68d142168d5189c6ee2b87c6d8a2299d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 9 May 2025 08:01:57 +0700 Subject: (andri) delete chatter pada product pricelist + item --- indoteknik_custom/models/product_pricelist.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/product_pricelist.py b/indoteknik_custom/models/product_pricelist.py index 46d12f4d..94a9b239 100644 --- a/indoteknik_custom/models/product_pricelist.py +++ b/indoteknik_custom/models/product_pricelist.py @@ -3,8 +3,7 @@ from datetime import datetime, timedelta class ProductPricelist(models.Model): - _name = 'product.pricelist' - _inherit = ['product.pricelist', 'mail.thread', 'mail.activity.mixin'] + _inherit = 'product.pricelist' is_flash_sale = fields.Boolean(string='Flash Sale', default=False) is_show_program = fields.Boolean(string='Show Program', default=False) @@ -57,8 +56,7 @@ class ProductPricelist(models.Model): return tier_name class ProductPricelistItem(models.Model): - _name = 'product.pricelist.item' - _inherit = ['product.pricelist.item', 'mail.thread', 'mail.activity.mixin'] + _inherit = 'product.pricelist.item' manufacture_id = fields.Many2one('x_manufactures', string='Manufacture') computed_price = fields.Float(string='Computed Price') \ No newline at end of file -- cgit v1.2.3 From 09e83469a30d7907b6b6e605dabda879250ef72d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 9 May 2025 08:06:41 +0700 Subject: (andri) delete chatter pada view product pricelist & item --- indoteknik_custom/views/product_pricelist.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/indoteknik_custom/views/product_pricelist.xml b/indoteknik_custom/views/product_pricelist.xml index 6a1111cf..3c2b8b8d 100644 --- a/indoteknik_custom/views/product_pricelist.xml +++ b/indoteknik_custom/views/product_pricelist.xml @@ -25,12 +25,6 @@ - -
    - - -
    -
    -- cgit v1.2.3 From 0d34982dfd6d644e87733459bf0de5c05aad7cb6 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 9 May 2025 08:06:55 +0700 Subject: (andri) delete chatter pada view product pricelist & item --- indoteknik_custom/views/product_pricelist_item.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/indoteknik_custom/views/product_pricelist_item.xml b/indoteknik_custom/views/product_pricelist_item.xml index 86ee9389..973ae181 100755 --- a/indoteknik_custom/views/product_pricelist_item.xml +++ b/indoteknik_custom/views/product_pricelist_item.xml @@ -11,12 +11,6 @@ product.pricelist.item - -
    - - -
    -
    -- cgit v1.2.3 From 0f7e495ae7a28bd897929322e0419de91ecb1675 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 9 May 2025 11:21:46 +0700 Subject: (andri) penyesuaian log note pada purchase pricelist & handle perubahan log note image di product & variant (only notif add/update/delete) --- indoteknik_custom/models/product_template.py | 170 +++++++++++++++---------- indoteknik_custom/models/purchase_pricelist.py | 41 ++++-- 2 files changed, 127 insertions(+), 84 deletions(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 99ae73d3..373b8ebd 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -402,6 +402,11 @@ class ProductTemplate(models.Model): # 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 = { + 'image_1920': 'Main Image', + 'image_carousel_lines': 'Carousel Images', + 'product_template_image_ids': 'Extra Product Media', + } for record in self: changes = [] @@ -410,11 +415,11 @@ class ProductTemplate(models.Model): continue field = record._fields[field_name] - field_label = field.string or 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): + def stringify(val, field, record): if val in [None, False]: return 'None' if isinstance(field, fields.Selection): @@ -425,25 +430,40 @@ class ProductTemplate(models.Model): if isinstance(field, fields.Boolean): return 'Yes' if val else 'No' if isinstance(field, fields.Many2one): - return val.name if hasattr(val, 'name') else str(val) + if isinstance(val, int): + rec = record.env[field.comodel_name].browse(val) + return rec.display_name if rec.exists() else str(val) + elif isinstance(val, models.BaseModel): + return val.display_name + return str(val) if isinstance(field, fields.Many2many): - records = val if isinstance(val, models.Model) else val - names = [] - if records: - for attr in ['name', 'x_name', 'display_name']: - if hasattr(records[0], attr): - names = records.mapped(attr) - break - if not names: - names = [str(r.id) for r in records] - return ", ".join(names) if names else 'None' + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' + for attr in ['name', 'x_name', 'display_name']: + 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) - new_val_str = stringify(new_value) + old_val_str = stringify(old_value, field, record) + new_val_str = stringify(new_value, field, record) if old_val_str != new_val_str: - changes.append(f"
  • {field_label}: '{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"
  • {field_label}: image added
  • ") + elif old_val_str != 'None' and new_val_str == 'None': + changes.append(f"
  • {field_label}: image removed
  • ") + else: + changes.append(f"
  • {field_label}: image updated
  • ") + else: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") if changes: message = "Updated:
      %s
    " % "".join(changes) @@ -793,64 +813,74 @@ class ProductProduct(models.Model): # log perubahan field def _log_field_changes(self, vals, old_values): - exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] - - 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 = field.string or field_name - old_value = old_values.get(record.id, {}).get(field_name) - new_value = record[field_name] + exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] + # for image fields, use custom labels + custom_labels = { + 'image_1920': 'Main Image', + 'image_carousel_lines': 'Carousel Images', + 'product_template_image_ids': 'Extra Product Media', + } - def stringify(val): - if val in [None, False]: - return 'None' - - if isinstance(field, fields.Selection): - selection = field.selection - if callable(selection): - selection = selection(record) - return dict(selection).get(val, str(val)) - - if isinstance(field, fields.Boolean): - return 'Yes' if val else 'No' - - if isinstance(field, fields.Many2one): - if isinstance(val, int): - rec = self.env[field.comodel_name].browse(val) - return rec.display_name if rec.exists() else str(val) - elif isinstance(val, models.BaseModel): - return val.display_name - return str(val) - - if isinstance(field, fields.Many2many): - records = record[field_name] - names = [] - if records: + 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): + if val in [None, False]: + return 'None' + if isinstance(field, fields.Selection): + selection = field.selection + if callable(selection): + selection = selection(record) + return dict(selection).get(val, str(val)) + if isinstance(field, fields.Boolean): + return 'Yes' if val else 'No' + if isinstance(field, fields.Many2one): + if isinstance(val, int): + rec = record.env[field.comodel_name].browse(val) + return rec.display_name if rec.exists() else str(val) + elif isinstance(val, models.BaseModel): + return val.display_name + return str(val) + if isinstance(field, fields.Many2many): + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' for attr in ['name', 'x_name', 'display_name']: if hasattr(records[0], attr): - names = records.mapped(attr) - break - if not names: - names = [str(r.id) for r in records] - return ", ".join(names) if names else 'None' - - return str(val) - - - old_val_str = stringify(old_value) - new_val_str = stringify(new_value) - - if old_val_str != new_val_str: - changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + 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) - if changes: - message = "Updated:
      %s
    " % "".join(changes) - record.message_post(body=message) + old_val_str = stringify(old_value, field, record) + new_val_str = stringify(new_value, field, record) + + if old_val_str != new_val_str: + if field_name in custom_labels: # handle image field + if old_val_str == 'None' and new_val_str != 'None': + changes.append(f"
  • {field_label}: image added
  • ") + elif old_val_str != 'None' and new_val_str == 'None': + changes.append(f"
  • {field_label}: image removed
  • ") + else: + changes.append(f"
  • {field_label}: image updated
  • ") + else: + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + + if changes: + message = "Updated:
      %s
    " % "".join(changes) + record.message_post(body=message) # simpan data lama dan log perubahan field def write(self, vals): diff --git a/indoteknik_custom/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py index 6ac74c69..764911cf 100755 --- a/indoteknik_custom/models/purchase_pricelist.py +++ b/indoteknik_custom/models/purchase_pricelist.py @@ -146,33 +146,46 @@ class PurchasePricelist(models.Model): old_value = old_values.get(record.id, {}).get(field_name) new_value = record[field_name] - def stringify(val): + def stringify(val, field, record): if val in [None, False]: return 'None' + # Handle Selection if isinstance(field, fields.Selection): selection = field.selection if callable(selection): selection = selection(record) return dict(selection).get(val, str(val)) + # Handle Boolean if isinstance(field, fields.Boolean): return 'Yes' if val else 'No' + # Handle Many2one if isinstance(field, fields.Many2one): - return val.name if hasattr(val, 'name') else str(val) + if isinstance(val, int): + rec = record.env[field.comodel_name].browse(val) + return rec.display_name if rec.exists() else str(val) + elif isinstance(val, models.BaseModel): + return val.display_name + return str(val) + # Handle Many2many if isinstance(field, fields.Many2many): - records = val if isinstance(val, models.Model) else val - names = [] - if records: - for attr in ['name', 'x_name', 'display_name']: - if hasattr(records[0], attr): - names = records.mapped(attr) - break - if not names: - names = [str(r.id) for r in records] - return ", ".join(names) if names else 'None' + records = val if isinstance(val, models.BaseModel) else record[field.name] + if not records: + return 'None' + for attr in ['name', 'x_name', 'display_name']: + if hasattr(records[0], attr): + return ", ".join(records.mapped(attr)) + return ", ".join(str(r.id) for r in records) + # Handle One2many + 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)})" + # Default case (Char, Float, Integer, etc) return str(val) - old_val_str = stringify(old_value) - new_val_str = stringify(new_value) + old_val_str = stringify(old_value, field, record) + new_val_str = stringify(new_value, field, record) if old_val_str != new_val_str: changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") -- cgit v1.2.3 From 474755658e732235a7855810b993184c23f75386 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 9 May 2025 15:20:00 +0700 Subject: (andri) penyesuaian log note untuk perubahan nilai pada tabel vendor pricelist & product attribute --- indoteknik_custom/models/product_template.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 373b8ebd..e95060e6 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -406,6 +406,8 @@ class ProductTemplate(models.Model): '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', } for record in self: @@ -457,11 +459,11 @@ class ProductTemplate(models.Model): 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"
  • {field_label}: image added
  • ") + changes.append(f"
  • {field_label}: added
  • ") elif old_val_str != 'None' and new_val_str == 'None': - changes.append(f"
  • {field_label}: image removed
  • ") + changes.append(f"
  • {field_label}: removed
  • ") else: - changes.append(f"
  • {field_label}: image updated
  • ") + changes.append(f"
  • {field_label}: updated
  • ") else: changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") -- cgit v1.2.3 From 0362205a2ac1404fffccdd02691f121a4ffac76e Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 10 May 2025 11:59:11 +0700 Subject: (andri) perapihan log note product di bagian vendor dan product attribute serta penyesuaian yang lain --- indoteknik_custom/models/product_template.py | 476 ++++++++++++++++++++++++--- 1 file 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"
  • {label}: image added
  • " + elif old_val != 'None' and new_val == 'None': + return f"
  • {label}: image removed
  • " + elif old_val != new_val: + return f"
  • {label}: image updated
  • " + 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"Product Attribute:
    {attribute_name} added
    " + 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"Product Attribute:
    {attribute_name} updated
    " + message += "
    ".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"Product Attribute:
    {attribute_name} removed
    " + if values: + message += f"Values: '{values}'" + self.message_post(body=message) + + elif cmd[0] == 5: # Clear all + self.message_post(body=f"Product Attribute:
    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"
  • Price: {vals['price']}
  • ") + if 'min_qty' in vals and vals['min_qty'] is not None: + details.append(f"
  • Quantity: {vals['min_qty']}
  • ") + if 'delay' in vals and vals['delay'] is not None: + details.append(f"
  • Delivery Lead Time: {vals['delay']}
  • ") + if 'product_name' in vals and vals['product_name']: + details.append(f"
  • Vendor Product Name: {vals['product_name']}
  • ") + if 'product_code' in vals and vals['product_code']: + details.append(f"
  • Vendor Product Code: {vals['product_code']}
  • ") + if 'currency_id' in vals and vals['currency_id']: + currency = self.env['res.currency'].browse(vals['currency_id']) + details.append(f"
  • Currency: {currency.name}
  • ") + if 'product_uom' in vals and vals['product_uom']: + uom = self.env['uom.uom'].browse(vals['product_uom']) + details.append(f"
  • Unit of Measure: {uom.name}
  • ") + + if details: + detail_str = f" with:
      {''.join(details)}
    " + else: + detail_str = "" + + self.message_post(body=f"Vendor Pricelist: 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"
  • Vendor: {old_name} → {new_name}
  • ") + 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"
  • {label}: {old_str} → {new_str}
  • ") + + if changes: + changes_str = f"
      {''.join(changes)}
    " + self.message_post(body=f"Vendor Pricelist: 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"
  • Price: {vendor_data['price']}
  • ") + if vendor_data.get('min_qty'): + details.append(f"
  • Quantity: {vendor_data['min_qty']}
  • ") + if vendor_data.get('product_name'): + details.append(f"
  • Product Name: {vendor_data['product_name']}
  • ") + if vendor_data.get('delay'): + details.append(f"
  • Delivery Lead Time: {vendor_data['delay']}
  • ") + + if details: + detail_str = f"
      {''.join(details)}
    " + 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"
  • Price: {rec.price}
  • ") + if rec.min_qty: + details.append(f"
  • Quantity: {rec.min_qty}
  • ") + if rec.product_name: + details.append(f"
  • Product Name: {rec.product_name}
  • ") + + if details: + detail_str = f"
      {''.join(details)}
    " + else: + detail_str = "" + else: + name = f"ID {cmd[1]}" + detail_str = "" + + self.message_post(body=f"Vendor Pricelist: removed '{name}'{detail_str}") + + elif cmd[0] == 5: # Clear all + self.message_post(body=f"Vendor Pricelist: 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"
  • {field_label}: added
  • ") - elif old_val_str != 'None' and new_val_str == 'None': - changes.append(f"
  • {field_label}: removed
  • ") - else: - changes.append(f"
  • {field_label}: updated
  • ") - else: - changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") + field_label = field.string or field_name + changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") if changes: - message = "Updated:
      %s
    " % "".join(changes) - record.message_post(body=message) + record.message_post(body=f"Updated:
      {''.join(changes)}
    ") # 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): -- cgit v1.2.3 From ad3eb842d881ad89b0239075695dc2ccf424031f Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 10 May 2025 15:36:55 +0700 Subject: (andri) add log note PO Confirm di purchase Pricelist --- indoteknik_custom/models/purchase_order.py | 122 ++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 98b367d0..a3e2c388 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -580,10 +580,11 @@ class PurchaseOrder(models.Model): purchase_pricelist = self.env['purchase.pricelist'].search([ ('product_id', '=', line.product_id.id), ('vendor_id', '=', line.order_id.partner_id.id) - ]) - purchase_pricelist = purchase_pricelist.with_context(update_by='system') + ]) + if not purchase_pricelist: - purchase_pricelist.create([{ + # Buat pricelist baru dengan context + new_pricelist = self.env['purchase.pricelist'].with_context(update_by='system').create([{ 'vendor_id': line.order_id.partner_id.id, 'product_id': line.product_id.id, 'product_price': 0, @@ -591,12 +592,52 @@ class PurchaseOrder(models.Model): 'system_price': price_unit, 'system_last_update': current_time }]) + + # Buat lognote untuk pricelist baru + message = f""" + New Purchase Pricelist Created from PO
    + PO: {line.order_id.name}
    + System Price: {price_unit:,.2f}
    + System Tax: {taxes.name if taxes else 'No Tax'}
    + System Update: {current_time}
    + """ + new_pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id) else: + # Simpan nilai lama untuk logging + old_values = { + 'system_price': purchase_pricelist.system_price, + 'taxes_system_id': purchase_pricelist.taxes_system_id, + } + + # Update dengan context + purchase_pricelist = purchase_pricelist.with_context(update_by='system') purchase_pricelist.write({ 'system_last_update': current_time, 'taxes_system_id': taxes.id, 'system_price': price_unit }) + + # Buat lognote jika ada perubahan + changes = [] + if old_values['system_price'] != price_unit: + changes.append(f"
  • System Price: {old_values['system_price']:,.2f} → {price_unit:,.2f}
  • ") + if old_values['taxes_system_id'] != taxes: + old_tax_name = old_values['taxes_system_id'].name if old_values['taxes_system_id'] else 'No Tax' + new_tax_name = taxes.name if taxes else 'No Tax' + changes.append(f"
  • System Tax: {old_tax_name} → {new_tax_name}
  • ") + + if changes: + message = f""" + System Fields Updated from PO
    + PO: {line.order_id.name}
    + Changes: +
      + {"".join(changes)} +
    • System Update: {current_time}
    • +
    + Updated By: {self.env.user.name} + """ + purchase_pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id) def _compute_date_planned(self): for order in self: @@ -1198,8 +1239,83 @@ class PurchaseOrder(models.Model): if not self.env.context.get('skip_check_payment'): self.with_context(skip_check_payment=True)._check_payment_term() self.with_context(notify_tax=True)._check_tax_rule() + # Tambahkan pemanggilan method untuk handle pricelist system update + self._handle_pricelist_system_update(vals) return res + def _handle_pricelist_system_update(self, vals): + if 'order_line' in vals or any(key in vals for key in ['state', 'approval_status']): + for order in self: + # Hanya proses jika PO sudah approved + if order.state in ['purchase', 'done'] and order.approval_status == 'approved': + self._process_pricelist_update(order) + + def _process_pricelist_update(self, order): + for line in order.order_line: + pricelist = self._get_related_pricelist(line.product_id, order.partner_id) + + if pricelist: + # Simpan nilai lama + old_values = self._get_pricelist_old_values(pricelist) + + # Update dan cek perubahan + self._update_and_log_pricelist(pricelist, line, old_values) + + def _get_related_pricelist(self, product_id, vendor_id): + return self.env['purchase.pricelist'].search([ + ('product_id', '=', product_id.id), + ('vendor_id', '=', vendor_id.id) + ], limit=1) + + def _get_pricelist_old_values(self, pricelist): + return { + 'system_price': pricelist.system_price, + 'taxes_system_id': pricelist.taxes_system_id, + 'system_last_update': pricelist.system_last_update + } + + def _update_and_log_pricelist(self, pricelist, po_line, old_values): + changes = [] + current_time = fields.Datetime.now() + + # Cek perubahan System Price + if pricelist.system_price != po_line.price_unit: + if old_values['system_price'] != po_line.price_unit: + changes.append(f"
  • System Price: {old_values['system_price']:,.2f} → {po_line.price_unit:,.2f}
  • ") + + # Cek perubahan System Tax + if pricelist.taxes_system_id != po_line.taxes_id: + old_tax = old_values['taxes_system_id'] + old_tax_name = old_tax.name if old_tax else 'No Tax' + new_tax_name = po_line.taxes_id.name if po_line.taxes_id else 'No Tax' + if old_tax != po_line.taxes_id: + changes.append(f"
  • System Tax: {old_tax_name} → {new_tax_name}
  • ") + + # Update fields jika ada perubahan + if changes: + pricelist.with_context(update_by='system').write({ + 'system_price': po_line.price_unit, + 'taxes_system_id': po_line.taxes_id.id if po_line.taxes_id else False, + 'system_last_update': current_time + }) + + # Buat lognote + self._create_pricelist_lognote(pricelist, po_line, changes, current_time) + + def _create_pricelist_lognote(self, pricelist, po_line, changes, timestamp): + message = f""" + System Fields Updated from PO
    + PO: {po_line.order_id.name}
    + Changes: +
      + {"".join(changes)} +
    • System Update: {timestamp}
    • +
    + Updated By: {self.env.user.name} + """ + + pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id) + class PurchaseOrderUnlockWizard(models.TransientModel): _name = 'purchase.order.unlock.wizard' _description = 'Wizard untuk memberikan alasan unlock PO' -- cgit v1.2.3 From 360c47384d876d17725be8ff3ca6b83a9078615b Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 13 May 2025 10:10:34 +0700 Subject: (andri) revisi current date pada log note purchase pricelist --- indoteknik_custom/models/purchase_order.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index a3e2c388..c5ba5792 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -562,7 +562,9 @@ class PurchaseOrder(models.Model): i = 0 for line in self.order_line: i += 1 - current_time = datetime.utcnow() + + utc_time = fields.Datetime.now() + current_time = utc_time.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S') # print(i, len(self.order_line)) price_unit = line.price_unit @@ -635,7 +637,6 @@ class PurchaseOrder(models.Model): {"".join(changes)}
  • System Update: {current_time}
  • - Updated By: {self.env.user.name} """ purchase_pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id) -- cgit v1.2.3 From 2c565f289de7d5827a7d9e09b4919ecffcf0437c Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 13 May 2025 13:19:34 +0700 Subject: (andri) add log note muncul di variant ketika ada perubahan di template --- indoteknik_custom/models/product_template.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 19de8bb7..1d4723a6 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -869,6 +869,11 @@ class ProductTemplate(models.Model): if changes: record.message_post(body=f"Updated:
      {''.join(changes)}
    ") + # log changes to product variants + variant_message = f"Updated:
      {''.join(changes)}
    " + for variant in record.product_variant_ids: + variant.message_post(body=variant_message) + # simpan data lama dan log perubahan field def write(self, vals): context = self._get_context_with_all_info(vals) -- cgit v1.2.3 From bf79c492c047b8b9e0aa7657959a6f94263765dd Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 14 May 2025 08:28:53 +0700 Subject: (andri) add log note di product ketika variant ada perubahan --- indoteknik_custom/models/product_template.py | 31 ++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 1d4723a6..2679fbfd 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -1230,6 +1230,8 @@ class ProductProduct(models.Model): 'product_template_image_ids': 'Extra Product Media', } + template_changes = {} + for record in self: changes = [] for field_name in vals: @@ -1288,8 +1290,33 @@ class ProductProduct(models.Model): changes.append(f"
  • {field_label}: '{old_val_str}' → '{new_val_str}'
  • ") if changes: - message = "Updated:
      %s
    " % "".join(changes) - record.message_post(body=message) + # Post message to variant + variant_message = "Updated:
      %s
    " % "".join(changes) + record.message_post(body=variant_message) + + # Group changes by template for posting to template + template_id = record.product_tmpl_id.id + if template_id not in template_changes: + template_changes[template_id] = {} + + # Store variant information including ID for creating clickable link + template_changes[template_id][record.id] = { + 'name': record.display_name or f"Variant {record.id}", + 'changes': changes + } + + # Post grouped messages to templates with clickable links using your format + for template_id, variants_data in template_changes.items(): + template = self.env['product.template'].browse(template_id) + if template.exists(): + template_message = "Variant Updates:
    " + + for variant_id, variant_data in variants_data.items(): + # Create clickable link using your format + variant_link = f"{variant_data['name']}
    " + template_message += f"{variant_link}
      {''.join(variant_data['changes'])}

    " + + template.message_post(body=template_message) # simpan data lama dan log perubahan field def write(self, vals): -- cgit v1.2.3