diff options
| author | Azka Nathan <darizkyfaz@gmail.com> | 2025-02-19 09:38:46 +0700 |
|---|---|---|
| committer | Azka Nathan <darizkyfaz@gmail.com> | 2025-02-19 09:38:46 +0700 |
| commit | 99b252edaefc372fcd4ef59e065284d8feeb669c (patch) | |
| tree | e8b782c355517eb7fe01ee6321fc36c7ba2672ef | |
| parent | c99bf4c49859450ce4cb081c920edda2077b3b1a (diff) | |
push
| -rwxr-xr-x | indoteknik_custom/models/__init__.py | 2 | ||||
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 1 | ||||
| -rw-r--r-- | indoteknik_custom/models/sales_order_koli.py | 25 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_backorder_confirmation.py | 50 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_picking.py | 171 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 2 | ||||
| -rwxr-xr-x | indoteknik_custom/views/sale_order.xml | 15 | ||||
| -rw-r--r-- | indoteknik_custom/views/stock_picking.xml | 6 |
8 files changed, 265 insertions, 7 deletions
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index ed9e91da..a5297806 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -140,3 +140,5 @@ from . import va_multi_reject from . import stock_immediate_transfer from . import coretax_fatur from . import barcoding_product +from . import sales_order_koli +from . import stock_backorder_confirmation diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 7b2d9bf8..88c32fb6 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -11,6 +11,7 @@ _logger = logging.getLogger(__name__) class SaleOrder(models.Model): _inherit = "sale.order" + koli_lines = fields.One2many('sales.order.koli', 'sale_order_id', string='Sales Order Koli', auto_join=True) fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2') fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment') reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines') diff --git a/indoteknik_custom/models/sales_order_koli.py b/indoteknik_custom/models/sales_order_koli.py new file mode 100644 index 00000000..02e85256 --- /dev/null +++ b/indoteknik_custom/models/sales_order_koli.py @@ -0,0 +1,25 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import AccessError, UserError, ValidationError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +import logging + +_logger = logging.getLogger(__name__) + + +class SalesOrderKoli(models.Model): + _name = 'sales.order.koli' + _description = 'Sales Order Koli' + _order = 'sale_order_id, id' + _rec_name = 'koli_id' + + sale_order_id = fields.Many2one( + 'sale.order', + string='Sale Order Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + koli_id = fields.Many2one('check.koli', string='Koli') + picking_id = fields.Many2one('stock.picking', string='Picking') + diff --git a/indoteknik_custom/models/stock_backorder_confirmation.py b/indoteknik_custom/models/stock_backorder_confirmation.py new file mode 100644 index 00000000..0fd7c34e --- /dev/null +++ b/indoteknik_custom/models/stock_backorder_confirmation.py @@ -0,0 +1,50 @@ +from odoo import models, fields, api +from odoo.tools.float_utils import float_compare + +class StockBackorderConfirmation(models.TransientModel): + _inherit = 'stock.backorder.confirmation' + + def process(self): + res = super(StockBackorderConfirmation, self).process() + + pickings_to_do = self.env['stock.picking'] + for line in self.backorder_confirmation_line_ids: + if line.to_backorder: + pickings_to_do |= line.picking_id + + for pick in pickings_to_do: + # Mencari backorder yang baru terbentuk + backorder = self.env['stock.picking'].search([('backorder_id', '=', pick.id)], limit=1) + + if backorder: + # Cari BU/OUT terbaru berdasarkan sale_id + latest_out_picking = self.env['stock.picking'].search([ + ('sale_id', '=', pick.sale_id.id), + ('picking_type_id.code', '=', 'outgoing') + ], order='id desc', limit=1) + + # Update linked_out_picking_id pada backorder BU/PICK + if latest_out_picking: + backorder.linked_out_picking_id = latest_out_picking.id + else: + backorder.linked_out_picking_id = pick.linked_out_picking_id + + # 🚀 Cek apakah ada backorder baru dari BU/OUT + for pick in self.env['stock.picking'].search([ + ('picking_type_id.code', '=', 'outgoing'), + ('backorder_id', '!=', False) + ]): + # Backorder BU/OUT terbaru + latest_out_backorder = self.env['stock.picking'].search([ + ('backorder_id', '=', pick.id) + ], order='id desc', limit=1) + + if latest_out_backorder: + # 🚀 Update semua BU/PICK yang belum `done` atau `cancel` + self.env['stock.picking'].search([ + ('sale_id', '=', pick.sale_id.id), + ('picking_type_id.code', '=', 'incoming'), + ('state', 'not in', ['done', 'cancel']) + ]).write({'linked_out_picking_id': latest_out_backorder.id}) + + return res diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3f888b02..02ce819f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -124,7 +124,7 @@ class StockPicking(models.Model): ('cancel', 'Cancelled'), ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") notee = fields.Text(string="Note") - quantity_koli = fields.Float(string="Quantity Koli") + quantity_koli = fields.Float(string="Quantity Koli", copy=False) source_koli_id = fields.Many2one('stock.picking', string="Source Koli") @api.model @@ -170,6 +170,54 @@ class StockPicking(models.Model): lalamove_image_url = fields.Char(string="Lalamove Image URL") lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html") + total_so_koli = fields.Integer(compute='_compute_total_so_koli', string="Total SO Koli") + total_koli = fields.Integer(compute='_compute_total_koli', string="Total Koli") + total_koli_display = fields.Char(compute='_compute_total_koli_display', string="Total Koli Display") + linked_out_picking_id = fields.Many2one('stock.picking', string="Linked BU/OUT", copy=False) + backorder_picking_id = fields.Many2one('stock.picking', string="Backorder Picking", copy=False) + + def action_create_backorder(self): + """ Override method to handle backorder logic automatically """ + backorder = super(StockPicking, self).action_create_backorder() + + for picking in self: + if 'BU/PICK/' in picking.name: + # Jika BU/PICK memiliki BU/OUT yang terhubung + if picking.linked_out_picking_id: + out_picking = picking.linked_out_picking_id + out_backorder = out_picking.backorder_picking_id + + # Jika BU/OUT belum punya backorder, hubungkan BU/PICK backorder ke BU/OUT lama + if not out_backorder: + backorder.linked_out_picking_id = out_picking + else: + # Jika BU/OUT sudah punya backorder, hubungkan ke backorder BU/OUT + backorder.linked_out_picking_id = out_backorder + + elif 'BU/OUT/' in picking.name: + # Jika BU/OUT membuat backorder, update semua BU/PICK yang terhubung ke BU/OUT lama + pickings_to_update = self.env['stock.picking'].search([('linked_out_picking_id', '=', picking.id)]) + for pick in pickings_to_update: + pick.linked_out_picking_id = backorder + + return backorder + + + @api.depends('total_so_koli') # Sesuaikan dengan field yang relevan + def _compute_total_so_koli(self): + for picking in self: + picking.total_so_koli = self.env['check.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id)]) + + @api.depends('total_koli') # Sesuaikan dengan field yang relevan + def _compute_total_koli(self): + for picking in self: + picking.total_koli = self.env['scan.koli'].search_count([('picking_id', '=', picking.id)]) + + @api.depends('total_koli', 'total_so_koli') + def _compute_total_koli_display(self): + for picking in self: + picking.total_koli_display = f"{picking.total_koli} / {picking.total_so_koli}" + @api.onchange('quantity_koli') def _onchange_quantity_koli(self): self.check_koli_lines = [(5, 0, 0)] @@ -772,6 +820,9 @@ class StockPicking(models.Model): raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): + if self.total_koli != self.total_so_koli: + raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.") + % (self.total_koli, self.total_so_koli)) if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") @@ -819,11 +870,40 @@ class StockPicking(models.Model): self.validation_minus_onhand_quantity() self.responsible = self.env.user.id + if self.picking_type_code == 'internal' and 'BU/PICK/' in self.name: + self.send_koli_to_so() + if self.picking_type_code == 'outgoing' and 'BU/OUT/' in self.name: + self.check_koli() res = super(StockPicking, self).button_validate() self.calculate_line_no() self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' return res + + + def check_koli(self): + for picking in self: + sale_id = picking.sale_id + for koli_lines in picking.scan_koli_lines: + if koli_lines.koli_id.sale_order_id != sale_id: + raise UserError('Koli tidak sesuai') + + def send_koli_to_so(self): + for picking in self: + for koli_line in picking.check_koli_lines: + existing_koli = self.env['sales.order.koli'].search([ + ('sale_order_id', '=', picking.sale_id.id), + ('picking_id', '=', picking.id), + ('koli_id', '=', koli_line.id) + ], limit=1) + + if not existing_koli: # Hindari duplikasi + self.env['sales.order.koli'].create({ + 'sale_order_id': picking.sale_id.id, + 'picking_id': picking.id, + 'koli_id': koli_line.id + }) + def check_qty_done_stock(self): @@ -901,6 +981,34 @@ class StockPicking(models.Model): res = super(StockPicking, self).action_cancel() return res + + def write(self, vals_list): + """ Override write method to auto-link BU/PICK to BU/OUT when necessary """ + records = super(StockPicking, self).write(vals_list) + for picking in records: + if 'BU/OUT/' in picking.name: + # Cari BU/PICK yang berhubungan berdasarkan logika tertentu + pick_picking = self.env['stock.picking'].search([ + ('name', 'like', 'BU/PICK/%'), + ('linked_out_picking_id', '=', False), + ('sale_id', '=', picking.sale_id.id) + ], limit=1) + + if pick_picking: + pick_picking.linked_out_picking_id = picking + + if 'BU/PICK/' in picking.name: + # Cari BU/PICK yang berhubungan berdasarkan logika tertentu + pick_picking = self.env['stock.picking'].search([ + ('name', 'like', 'BU/OUT/%'), + ('state', 'not in', ['cancel', 'done']), + ('sale_id', '=', picking.sale_id.id) + ], limit=1) + + if pick_picking: + pick_picking.linked_out_picking_id = picking + + return records @api.model @@ -917,7 +1025,20 @@ class StockPicking(models.Model): if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', vals.get('company_id'))]) > 0: new_name = f"{new_name}-DUP" vals['name'] = new_name - return super(StockPicking, self).create(vals) + records = super(StockPicking, self).create(vals) + for picking in records: + if 'BU/OUT/' in picking.name: + # Cari BU/PICK yang berhubungan berdasarkan logika tertentu + pick_picking = self.env['stock.picking'].search([ + ('name', 'like', 'BU/PICK'), + ('linked_out_picking_id', '=', False), + ('origin', '=', picking.origin) + ], limit=1) + + if pick_picking: + pick_picking.linked_out_picking_id = picking + + return records def write(self, vals): self._use_faktur(vals) @@ -1251,6 +1372,7 @@ class ScanKoli(models.Model): _name = 'scan.koli' _description = 'Scan Koli' _order = 'picking_id, id' + _rec_name = 'koli_id' picking_id = fields.Many2one( 'stock.picking', @@ -1260,8 +1382,45 @@ class ScanKoli(models.Model): index=True, copy=False, ) - koli_id = fields.Many2one('check.koli', string='Koli') + koli_id = fields.Many2one('sales.order.koli', string='Koli') + scan_koli_progress = fields.Char( + string="Progress Scan Koli", + compute="_compute_scan_koli_progress" + ) + + def _compute_scan_koli_progress(self): + """ Menghitung progres scan koli dalam format 'X/Y' """ + for scan in self: + if scan.picking_id: + all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') + scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan + total_so_koli = scan.picking_id.total_so_koli + scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" + + @api.model_create_multi + def create(self, vals_list): + """ Override create untuk update progress scan setelah scan koli ditambahkan """ + records = super(ScanKoli, self).create(vals_list) + for record in records: + record._compute_scan_koli_progress() + return records + + @api.constrains('picking_id', 'picking_id.total_so_koli') + def _check_koli_validation(self): + """ Validasi jika jumlah scan koli melebihi total SO koli """ + for scan in self: + total_scans = len(scan.picking_id.scan_koli_lines) + if total_scans > scan.picking_id.total_so_koli: + raise UserError(_("Jumlah scan koli melebihi total SO koli!")) + + @api.onchange('koli_id') + def _onchange_koli_id(self): + if not self.koli_id: + return + + source_koli_so = self.picking_id.ids # Picking asal dari Koli yang dipilih + source_koli = self.koli_id.picking_id.linked_out_picking_id.ids - @api.constrains('koli_id') - def _constrains_koli_id(self): - self.picking_id.source_koli_id = self.koli_id.picking_id.id
\ No newline at end of file + # Cek apakah source_koli ditemukan + if source_koli_so != source_koli: + raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index fa126492..3040ff2f 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -155,6 +155,8 @@ access_scan_koli,access.scan.koli,model_scan_koli,,1,1,1,1 access_stock_immediate_transfer,access.stock.immediate.transfer,model_stock_immediate_transfer,,1,1,1,1 access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1 access_purchase_order_unlock_wizard,access.purchase.order.unlock.wizard,model_purchase_order_unlock_wizard,,1,1,1,1 +access_sales_order_koli,access.sales.order.koli,model_sales_order_koli,,1,1,1,1 +access_stock_backorder_confirmation,access.stock.backorder.confirmation,model_stock_backorder_confirmation,,1,1,1,1 access_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pengajuan_tempo_line,,1,1,1,1 access_user_pengajuan_tempo,access.user.pengajuan.tempo,model_user_pengajuan_tempo,,1,1,1,1 diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 008a04ed..877208b0 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -280,6 +280,9 @@ <page string="Reject Line" name="page_sale_order_reject_line"> <field name="reject_line" readonly="1"/> </page> + <page string="Koli" name="page_sales_order_koli_line"> + <field name="koli_lines" readonly="1"/> + </page> </page> </field> </record> @@ -392,6 +395,18 @@ </data> <data> + <record id="sales_order_koli_tree" model="ir.ui.view"> + <field name="name">sales.order.koli.tree</field> + <field name="model">sales.order.koli</field> + <field name="arch" type="xml"> + <tree editable="top" create="false" delete="false"> + <field name="koli_id" readonly="1"/> + </tree> + </field> + </record> + </data> + + <data> </data> <record id="sales_order_fulfillment_v2_tree" model="ir.ui.view"> diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 42fa481d..2a11459c 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -73,7 +73,10 @@ <field name="dokumen_tanda_terima"/> <field name="dokumen_pengiriman"/> <field name="quantity_koli" attrs="{'invisible': [('location_dest_id', '!=', 60)]}"/> - <field name="source_koli_id" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}"/> + <!-- <field name="source_koli_id" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}"/> --> + <field name="total_koli_display" readonly="1"/> + <field name="linked_out_picking_id" readonly="1"/> + <field name="backorder_picking_id" readonly="1"/> </field> <field name="weight_uom_name" position="after"> <group> @@ -217,6 +220,7 @@ <field name="arch" type="xml"> <tree editable="bottom"> <field name="koli_id"/> + <field name="scan_koli_progress"/> </tree> </field> </record> |
