From 83af01630c9861789772ad997d2c495844ef6941 Mon Sep 17 00:00:00 2001 From: Alex Ardhian Date: Mon, 7 Sep 2026 15:45:17 +0700 Subject: [PATCH] ERP: field code kategori & item_code produk (parent+child+id+name), migrasi existing data, update .gitignore untuk addons --- .gitignore | 1 - addons/kosbar_erp_ext/__init__.py | 1 + addons/kosbar_erp_ext/__manifest__.py | 15 +++++ addons/kosbar_erp_ext/data/ir_sequence.xml | 31 ++++++++++ .../data/stock_picking_type.xml | 9 +++ addons/kosbar_erp_ext/models/__init__.py | 4 ++ .../kosbar_erp_ext/models/product_category.py | 19 +++++++ .../kosbar_erp_ext/models/product_template.py | 56 +++++++++++++++++++ addons/kosbar_erp_ext/models/sale_order.py | 29 ++++++++++ addons/kosbar_erp_ext/models/stock_picking.py | 10 ++++ .../security/ir.model.access.csv | 1 + addons/kosbar_erp_ext/security/res_groups.xml | 16 ++++++ scripts/migrate_item_code.py | 19 +++++++ 13 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 addons/kosbar_erp_ext/__init__.py create mode 100644 addons/kosbar_erp_ext/__manifest__.py create mode 100644 addons/kosbar_erp_ext/data/ir_sequence.xml create mode 100644 addons/kosbar_erp_ext/data/stock_picking_type.xml create mode 100644 addons/kosbar_erp_ext/models/__init__.py create mode 100644 addons/kosbar_erp_ext/models/product_category.py create mode 100644 addons/kosbar_erp_ext/models/product_template.py create mode 100644 addons/kosbar_erp_ext/models/sale_order.py create mode 100644 addons/kosbar_erp_ext/models/stock_picking.py create mode 100644 addons/kosbar_erp_ext/security/ir.model.access.csv create mode 100644 addons/kosbar_erp_ext/security/res_groups.xml create mode 100644 scripts/migrate_item_code.py diff --git a/.gitignore b/.gitignore index 16c8681..118203d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .env config/odoo.conf -addons/ *.log .DS_Store diff --git a/addons/kosbar_erp_ext/__init__.py b/addons/kosbar_erp_ext/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/addons/kosbar_erp_ext/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/addons/kosbar_erp_ext/__manifest__.py b/addons/kosbar_erp_ext/__manifest__.py new file mode 100644 index 0000000..b9b1672 --- /dev/null +++ b/addons/kosbar_erp_ext/__manifest__.py @@ -0,0 +1,15 @@ +{ + 'name': 'Kos Barokah ERP Extension', + 'version': '1.0', + 'category': 'Sales', + 'summary': 'Grup akses, Custom Sales/DO Sequence', + 'depends': ['sale_management', 'sales_team', 'stock', 'purchase', 'crm'], + 'data': [ + 'security/res_groups.xml', + 'security/ir.model.access.csv', + 'data/ir_sequence.xml', + 'data/stock_picking_type.xml', + ], + 'installable': True, + 'application': False, +} \ No newline at end of file diff --git a/addons/kosbar_erp_ext/data/ir_sequence.xml b/addons/kosbar_erp_ext/data/ir_sequence.xml new file mode 100644 index 0000000..af326ad --- /dev/null +++ b/addons/kosbar_erp_ext/data/ir_sequence.xml @@ -0,0 +1,31 @@ + + + + + + Quotation Kos Barokah + sale.quotation.custom + 5 + SQ/%(year)s/ + + + + + + Sales Order Kos Barokah + sale.order.custom + 5 + SO/%(year)s/ + + + + + + Delivery Order Kos Barokah + stock.picking.do + 5 + DO/%(year)s/ + + + + diff --git a/addons/kosbar_erp_ext/data/stock_picking_type.xml b/addons/kosbar_erp_ext/data/stock_picking_type.xml new file mode 100644 index 0000000..75e06df --- /dev/null +++ b/addons/kosbar_erp_ext/data/stock_picking_type.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/addons/kosbar_erp_ext/models/__init__.py b/addons/kosbar_erp_ext/models/__init__.py new file mode 100644 index 0000000..91e7030 --- /dev/null +++ b/addons/kosbar_erp_ext/models/__init__.py @@ -0,0 +1,4 @@ +from . import sale_order +from . import stock_picking +from . import product_category +from . import product_template diff --git a/addons/kosbar_erp_ext/models/product_category.py b/addons/kosbar_erp_ext/models/product_category.py new file mode 100644 index 0000000..862407e --- /dev/null +++ b/addons/kosbar_erp_ext/models/product_category.py @@ -0,0 +1,19 @@ +from odoo import models, fields, api + + +class ProductCategory(models.Model): + _inherit = 'product.category' + + code = fields.Char( + string='Category Code', + size=10, + help='Kode 2-digit otomatis dari ID kategori (contoh ID 1 -> "01")', + ) + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + for r in records: + if not r.code: + r.code = f'{r.id:02d}' + return records diff --git a/addons/kosbar_erp_ext/models/product_template.py b/addons/kosbar_erp_ext/models/product_template.py new file mode 100644 index 0000000..40e51d3 --- /dev/null +++ b/addons/kosbar_erp_ext/models/product_template.py @@ -0,0 +1,56 @@ +import re + +from odoo import models, fields, api +from odoo.exceptions import ValidationError + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + item_code = fields.Char( + string='Item Code', + index=True, + help='Kombinasi: parent_kode + kategori_kode + id_produk(4digit) + nama_produk_tanpa_spasi(lower)', + ) + + @api.constrains('item_code') + def _check_item_code_unique(self): + for rec in self: + if not rec.item_code: + continue + dup = self.search([('item_code', '=', rec.item_code), ('id', '!=', rec.id)], limit=1) + if dup: + raise ValidationError( + f'Item Code "{rec.item_code}" sudah dipakai oleh produk lain ' + 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' + + 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 + + # 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}' + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + records._generate_item_code() + 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 diff --git a/addons/kosbar_erp_ext/models/sale_order.py b/addons/kosbar_erp_ext/models/sale_order.py new file mode 100644 index 0000000..f27ed53 --- /dev/null +++ b/addons/kosbar_erp_ext/models/sale_order.py @@ -0,0 +1,29 @@ +from odoo import models, fields, api + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + def _get_quotation_sequence_id(self): + return self.env.ref('kosbar_erp_ext.seq_quotation').sudo() + + def _get_sales_order_sequence_id(self): + return self.env.ref('kosbar_erp_ext.seq_sales_order').sudo() + + def _get_delivery_sequence_id(self): + return self.env.ref('kosbar_erp_ext.seq_delivery_order').sudo() + + def action_confirm(self): + """Override confirmation to change number from SQ to SO.""" + for order in self: + # Quotation (draft) should have SQ prefix; confirm -> SO + seq = self._get_sales_order_sequence_id() + order.name = seq.with_date(order.date_order or fields.Date.context_today(self)).next_by_code() + return super().action_confirm() + + @api.model + def create(self, vals): + """When creating a quotation (draft), use SQ sequence.""" + if vals.get('state') == 'draft' and not vals.get('name'): + seq = self._get_quotation_sequence_id() + vals['name'] = seq.with_date(vals.get('date_order') or fields.Date.context_today(self)).next_by_code() + return super().create(vals) \ No newline at end of file diff --git a/addons/kosbar_erp_ext/models/stock_picking.py b/addons/kosbar_erp_ext/models/stock_picking.py new file mode 100644 index 0000000..c725cd5 --- /dev/null +++ b/addons/kosbar_erp_ext/models/stock_picking.py @@ -0,0 +1,10 @@ +from odoo import models, api, fields + +class StockPicking(models.Model): + _inherit = 'stock.picking' + + @api.model + def create(self, vals): + """Delivery order (outgoing) memakai sequence DO/2026/00001 dari + picking type sequence_id yang sudah diarahkan ke seq_delivery_order.""" + return super().create(vals) diff --git a/addons/kosbar_erp_ext/security/ir.model.access.csv b/addons/kosbar_erp_ext/security/ir.model.access.csv new file mode 100644 index 0000000..97dd8b9 --- /dev/null +++ b/addons/kosbar_erp_ext/security/ir.model.access.csv @@ -0,0 +1 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink diff --git a/addons/kosbar_erp_ext/security/res_groups.xml b/addons/kosbar_erp_ext/security/res_groups.xml new file mode 100644 index 0000000..4f2e34e --- /dev/null +++ b/addons/kosbar_erp_ext/security/res_groups.xml @@ -0,0 +1,16 @@ + + + + + + Sales Manager + + + + + + CFO / Finance Director + + + + diff --git a/scripts/migrate_item_code.py b/scripts/migrate_item_code.py new file mode 100644 index 0000000..7cca60b --- /dev/null +++ b/scripts/migrate_item_code.py @@ -0,0 +1,19 @@ +import re + +# Migration: isi code kategori & item_code produk untuk data existing +cats = env['product.category'].search([]) +for c in cats: + if not c.code: + c.code = f'{c.id:02d}' +env.cr.commit() +print(f'Kategori di-update: {len(cats)}') + +prods = env['product.template'].search([]) +for p in prods: + p._generate_item_code() +env.cr.commit() +print(f'Produk di-update: {len(prods)}') + +# Verifikasi contoh +for p in prods[:5]: + print(f' {p.name!r} -> {p.item_code!r}')