From 46e2d029081edde2b9068363f6f54a6aa6d88303 Mon Sep 17 00:00:00 2001 From: Alex Ardhian Date: Mon, 7 Sep 2026 16:41:16 +0700 Subject: [PATCH] update product --- addons/kosbar_erp_ext/__manifest__.py | 1 + addons/kosbar_erp_ext/models/__init__.py | 1 + .../kosbar_erp_ext/models/product_product.py | 13 ++ .../kosbar_erp_ext/models/product_template.py | 57 ++++++--- .../references/custom-product-code.md | 118 ++++++++++++++++++ addons/kosbar_erp_ext/views/product_views.xml | 27 ++++ 6 files changed, 197 insertions(+), 20 deletions(-) create mode 100644 addons/kosbar_erp_ext/models/product_product.py create mode 100644 addons/kosbar_erp_ext/references/custom-product-code.md create mode 100644 addons/kosbar_erp_ext/views/product_views.xml diff --git a/addons/kosbar_erp_ext/__manifest__.py b/addons/kosbar_erp_ext/__manifest__.py index b9b1672..3369167 100644 --- a/addons/kosbar_erp_ext/__manifest__.py +++ b/addons/kosbar_erp_ext/__manifest__.py @@ -9,6 +9,7 @@ 'security/ir.model.access.csv', 'data/ir_sequence.xml', 'data/stock_picking_type.xml', + 'views/product_views.xml', ], 'installable': True, 'application': False, diff --git a/addons/kosbar_erp_ext/models/__init__.py b/addons/kosbar_erp_ext/models/__init__.py index 91e7030..eac5bc4 100644 --- a/addons/kosbar_erp_ext/models/__init__.py +++ b/addons/kosbar_erp_ext/models/__init__.py @@ -2,3 +2,4 @@ from . import sale_order from . import stock_picking from . import product_category from . import product_template +from . import product_product diff --git a/addons/kosbar_erp_ext/models/product_product.py b/addons/kosbar_erp_ext/models/product_product.py new file mode 100644 index 0000000..40b11e9 --- /dev/null +++ b/addons/kosbar_erp_ext/models/product_product.py @@ -0,0 +1,13 @@ +from odoo import models, fields, api + + +class ProductProduct(models.Model): + _inherit = 'product.product' + + item_code = fields.Char( + string='Item Code', + related='product_tmpl_id.item_code', + store=True, + readonly=True, + help='Item Code dari template produk (variant menampilkan kode template).', + ) diff --git a/addons/kosbar_erp_ext/models/product_template.py b/addons/kosbar_erp_ext/models/product_template.py index 40e51d3..4595b44 100644 --- a/addons/kosbar_erp_ext/models/product_template.py +++ b/addons/kosbar_erp_ext/models/product_template.py @@ -9,10 +9,24 @@ class ProductTemplate(models.Model): item_code = fields.Char( string='Item Code', - index=True, - help='Kombinasi: parent_kode + kategori_kode + id_produk(4digit) + nama_produk_tanpa_spasi(lower)', + store=True, + readonly=False, # biar view bisa pakai; kita atur readonly di XML + help='Kombinasi: parent_kode + kategori_kode + id(+4 digit) + nama_tanpa_spasi(lower). ' + 'Read-only, dipakai sbg stock keeping unit code.', ) + @api.onchange('categ_id') + def _onchange_categ_preview(self): + """Saat create belum simpan id -> tampilkan pratinjau gabungan kode kategori.""" + if self.categ_id and not self.env.context.get('default_product_tmpl_id') \ + and not self.id and not self.item_code: + cat = self.categ_id + if cat and not cat.code: + cat.code = f'{cat.id:02d}' + parent = cat.parent_id.code if cat.parent_id else '' + own = cat.code or '' + self.item_code = f'{parent}{own}' + @api.constrains('item_code') def _check_item_code_unique(self): for rec in self: @@ -25,32 +39,35 @@ class ProductTemplate(models.Model): f'({dup.name}). Item Code harus unik.' ) - def _generate_item_code(self): - """Generate item_code: parent_code + cat_code + id:04d + name_slug""" - for p in self: - cat = p.categ_id - parent_code = '' - cat_code = '00' + def _category_prefix(self, cat): + """Return gabungan kode kategori (parent+own), 4 digit atau 00.""" + if not cat: + return '00' + if not cat.code: + cat.code = f'{cat.id:02d}' + parent_code = cat.parent_id.code if cat.parent_id else '' + own_code = cat.code or '' + return f'{parent_code}{own_code}' - if cat: - # Pastikan kategori punya code (calculate jika belum ada) - if not cat.code: - cat.code = f'{cat.id:02d}' - parent_code = cat.parent_id.code if cat.parent_id else '' - cat_code = cat.code + def _name_slug(self, name): + return re.sub(r'\s+', '', (name or '').lower()) - # slug: lowercase, remove semua spasi - name_slug = re.sub(r'\s+', '', (p.name or '').lower()) - p.item_code = f'{parent_code}{cat_code}{p.id:04d}{name_slug}' + def _full_item_code(self, obj): + """Compute full item_code: prefix kategori + id:04d + name_slug.""" + prefix = self._category_prefix(obj.categ_id) + oid = obj.id or 0 + return f'{prefix}{oid:04d}{self._name_slug(obj.name)}' @api.model_create_multi def create(self, vals_list): records = super().create(vals_list) - records._generate_item_code() + for r in records: + r.item_code = r._full_item_code(r) return records def write(self, vals): res = super().write(vals) if 'name' in vals or 'categ_id' in vals: - self._generate_item_code() - return res \ No newline at end of file + for r in self: + r.item_code = r._full_item_code(r) + return res diff --git a/addons/kosbar_erp_ext/references/custom-product-code.md b/addons/kosbar_erp_ext/references/custom-product-code.md new file mode 100644 index 0000000..defb87c --- /dev/null +++ b/addons/kosbar_erp_ext/references/custom-product-code.md @@ -0,0 +1,118 @@ +# Custom Product Code & Item Code Automation + +## Overview +Odoo 17/19 Community tidak punya field `code` standar untuk product category. Untuk kebutuhan stock keeping unit (SKU) yang unik, kita buat field `item_code` yang di‑generate otomatis dari: + +``` +item_code = parent_category_code (2 digit) + category_code (2 digit) + product_id (4 digit) + name_slug (lowercase, no space) +``` + +Contoh: +- Kategori anak: id=06, parent=01 → prefix = `0106` +- Produk: id=3, nama="Backup BRT 30 x 36 x 1,25 mm" +- Hasil: `01060003backupbrt30x36x1,25mm` + +## Implementation Steps + +### 1. Extend `product.category` → field `code` +```python +class ProductCategory(models.Model): + _inherit = 'product.category' + code = fields.Char(string='Category Code', compute='_compute_code', store=True, size=4) + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + for r in records: + r.code = f'{r.id:02d}' + return records +``` + +### 2. Extend `product.template` → field `item_code` +Field `item_code`: +- **Stored** (`store=True`) — dipakai untuk pencarian & unique index. +- **Read‑only di view** — tidak boleh ditulis manual. +- **On‑change preview** → tampilkan prefix kategori saat buat produk baru. + +```python +class ProductTemplate(models.Model): + _inherit = 'product.template' + + item_code = fields.Char(string='Item Code', store=True, readonly=False) + + _sql_constraints = [ + ('item_code_unique', 'UNIQUE(item_code)', 'Item Code harus unik.'), + ] + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + for r in records: + r.item_code = r._full_item_code(r) + return records + + def write(self, vals): + res = super().write(vals) + if 'name' in vals or 'categ_id' in vals: + for r in self: + r.item_code = r._full_item_code(r) + return res +``` + +### 3. Extend `product.product` (varian) +Untuk produk ber‑varian, `item_code` diturunkan via related field ke template: + +```python +class ProductProduct(models.Model): + _inherit = 'product.product' + + item_code = fields.Char( + string='Item Code', + related='product_tmpl_id.item_code', + store=True, + readonly=True, + ) +``` + +### 4. XML View — field read‑only +```xml + + + + +``` + +### 5. PostgreSQL — Unique Index (optional, backup) +Constraint `__sql_constraints` sudah menambah unique index via Odoo ORM. Untuk kecepatan lebih cepat di PostgreSQL level, dapat manual: + +```sql +CREATE UNIQUE INDEX IF NOT EXISTS product_template_item_code_uniq +ON product_template (item_code) WHERE item_code IS NOT NULL; +``` + +### 6. Migration — isi data existing +```python +env.cr.execute(""" + CREATE UNIQUE INDEX IF NOT EXISTS product_template_item_code_uniq + ON product_template (item_code) WHERE item_code IS NOT NULL +""") + +cats = env['product.category'].search([]) +for c in cats: + if not c.code: + c.code = f'{c.id:02d}' + +prods = env['product.template'].search([]) +for p in prods: + p.item_code = p._full_item_code(p) +``` + +## Pitfalls +- `@api.depends('id')` **not allowed** di Odoo 19. Namun `id` tersedia di `create()` setelah super() selesai. +- Field `code` di kategori **harus `size` ditentukan** ( Odoo 17+ lint error tanpa `size`) +- Product dengan `categ_id = False` → gunakan fallback `00`. +- **Item Code read‑only di view**: bila pakai `store=True`, field tetap dapat ditulis via ORM (API). Untuk mencegah manual edit, gunakan `readonly=True` + `@api.constrains` untuk validasi. + +## References +- `models/product_template.py` — contoh implementasi lengkap (sudah ada di addon `kosbar_erp_ext`) +- `views/product_views.xml` — XML view inheritance \ No newline at end of file diff --git a/addons/kosbar_erp_ext/views/product_views.xml b/addons/kosbar_erp_ext/views/product_views.xml new file mode 100644 index 0000000..effd975 --- /dev/null +++ b/addons/kosbar_erp_ext/views/product_views.xml @@ -0,0 +1,27 @@ + + + + + product.template.form.item.code + product.template + + + + + + + + + + + product.product.form.item.code + product.product + + + + + + + +