From 8c87a6c35b2242ee1804e1955bbb216c8c86de4d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 10 Feb 2025 09:44:05 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cc86c451..ec761900 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1217,4 +1217,19 @@ class BarcodeProduct(models.Model): if record.barcode and not record.product_id.barcode: record.product_id.barcode = record.barcode else: - raise UserError('Barcode sudah terisi') \ No newline at end of file + raise UserError('Barcode sudah terisi') + +class CheckKoli(models.Model): + _name = 'check.koli' + _description = 'Check Koli' + _order = 'picking_id, id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + product_id = fields.(string='Koli') \ No newline at end of file -- cgit v1.2.3 From c99bf4c49859450ce4cb081c920edda2077b3b1a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 11 Feb 2025 09:48:55 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 34 +++++++++++++++++++++++++- indoteknik_custom/security/ir.model.access.csv | 2 ++ indoteknik_custom/views/stock_picking.xml | 28 +++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ec761900..3f888b02 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -16,6 +16,8 @@ _logger = logging.getLogger(__name__) class StockPicking(models.Model): _inherit = 'stock.picking' + scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True) + check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True) check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True) barcode_product_lines = fields.One2many('barcode.product', 'picking_id', string='Barcode Product', auto_join=True) is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') @@ -122,6 +124,8 @@ 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") + source_koli_id = fields.Many2one('stock.picking', string="Source Koli") @api.model def _compute_dokumen_tanda_terima(self): @@ -166,6 +170,14 @@ 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") + @api.onchange('quantity_koli') + def _onchange_quantity_koli(self): + self.check_koli_lines = [(5, 0, 0)] + self.check_koli_lines = [(0, 0, { + 'koli': f"{self.name}/{str(i+1).zfill(3)}", + 'picking_id': self.id, + }) for i in range(int(self.quantity_koli))] + def _compute_lalamove_image_html(self): for record in self: if record.lalamove_image_url: @@ -1223,6 +1235,7 @@ class CheckKoli(models.Model): _name = 'check.koli' _description = 'Check Koli' _order = 'picking_id, id' + _rec_name = 'koli' picking_id = fields.Many2one( 'stock.picking', @@ -1232,4 +1245,23 @@ class CheckKoli(models.Model): index=True, copy=False, ) - product_id = fields.(string='Koli') \ No newline at end of file + koli = fields.Char(string='Koli') + +class ScanKoli(models.Model): + _name = 'scan.koli' + _description = 'Scan Koli' + _order = 'picking_id, id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + koli_id = fields.Many2one('check.koli', string='Koli') + + @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 diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 73877052..fa126492 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -150,6 +150,8 @@ access_v_move_outstanding,access.v.move.outstanding,model_v_move_outstanding,,1, access_va_multi_approve,access.va.multi.approve,model_va_multi_approve,,1,1,1,1 access_va_multi_reject,access.va.multi.reject,model_va_multi_reject,,1,1,1,1 access_check_product,access.check.product,model_check_product,,1,1,1,1 +access_check_koli,access.check.koli,model_check_koli,,1,1,1,1 +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 diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 1c6ae8f7..42fa481d 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -72,6 +72,8 @@ + + @@ -198,11 +200,37 @@ + + + + + + + + scan.koli.tree + scan.koli + + + + + + + + + check.koli.tree + check.koli + + + + + + + check.product.tree check.product -- cgit v1.2.3 From 99b252edaefc372fcd4ef59e065284d8feeb669c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Feb 2025 09:38:46 +0700 Subject: push --- indoteknik_custom/models/__init__.py | 2 + indoteknik_custom/models/sale_order.py | 1 + indoteknik_custom/models/sales_order_koli.py | 25 +++ .../models/stock_backorder_confirmation.py | 50 ++++++ indoteknik_custom/models/stock_picking.py | 171 ++++++++++++++++++++- indoteknik_custom/security/ir.model.access.csv | 2 + indoteknik_custom/views/sale_order.xml | 15 ++ indoteknik_custom/views/stock_picking.xml | 6 +- 8 files changed, 265 insertions(+), 7 deletions(-) create mode 100644 indoteknik_custom/models/sales_order_koli.py create mode 100644 indoteknik_custom/models/stock_backorder_confirmation.py 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 @@ + + + @@ -391,6 +394,18 @@ + + + sales.order.koli.tree + sales.order.koli + + + + + + + + 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 @@ - + + + + @@ -217,6 +220,7 @@ + -- cgit v1.2.3 From c1810b315d820a184db47d551b39700ce00d1440 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Feb 2025 09:57:56 +0700 Subject: push wms --- indoteknik_custom/models/sales_order_koli.py | 1 + .../models/stock_backorder_confirmation.py | 63 +++---- .../models/stock_immediate_transfer.py | 8 +- indoteknik_custom/models/stock_picking.py | 192 +++++++++------------ indoteknik_custom/views/sale_order.xml | 2 + indoteknik_custom/views/stock_picking.xml | 5 +- 6 files changed, 110 insertions(+), 161 deletions(-) diff --git a/indoteknik_custom/models/sales_order_koli.py b/indoteknik_custom/models/sales_order_koli.py index 02e85256..c782a40e 100644 --- a/indoteknik_custom/models/sales_order_koli.py +++ b/indoteknik_custom/models/sales_order_koli.py @@ -22,4 +22,5 @@ class SalesOrderKoli(models.Model): ) koli_id = fields.Many2one('check.koli', string='Koli') picking_id = fields.Many2one('stock.picking', string='Picking') + state = fields.Selection([('not_delivered', 'Not Delivered'), ('delivered', 'Delivered')], string='Status', default='not_delivered') diff --git a/indoteknik_custom/models/stock_backorder_confirmation.py b/indoteknik_custom/models/stock_backorder_confirmation.py index 0fd7c34e..f4da4cb5 100644 --- a/indoteknik_custom/models/stock_backorder_confirmation.py +++ b/indoteknik_custom/models/stock_backorder_confirmation.py @@ -5,46 +5,29 @@ class StockBackorderConfirmation(models.TransientModel): _inherit = 'stock.backorder.confirmation' def process(self): - res = super(StockBackorderConfirmation, self).process() - pickings_to_do = self.env['stock.picking'] + pickings_not_to_do = self.env['stock.picking'] for line in self.backorder_confirmation_line_ids: - if line.to_backorder: + line.picking_id.send_mail_bills() + line.picking_id.send_koli_to_so() + if line.to_backorder is True: 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 + else: + pickings_not_to_do |= line.picking_id + + for pick_id in pickings_not_to_do: + moves_to_log = {} + for move in pick_id.move_lines: + if float_compare(move.product_uom_qty, + move.quantity_done, + precision_rounding=move.product_uom.rounding) > 0: + moves_to_log[move] = (move.quantity_done, move.product_uom_qty) + pick_id._log_less_quantities_than_expected(moves_to_log) + + pickings_to_validate = self.env.context.get('button_validate_picking_ids') + if pickings_to_validate: + pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate).with_context(skip_backorder=True) + if pickings_not_to_do: + pickings_to_validate = pickings_to_validate.with_context(picking_ids_not_to_backorder=pickings_not_to_do.ids) + return pickings_to_validate.button_validate() + return True diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index 4be0dff2..ec00df7b 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -5,25 +5,25 @@ class StockImmediateTransfer(models.TransientModel): _inherit = 'stock.immediate.transfer' def process(self): - """Override process method to add send_mail_bills logic.""" pickings_to_do = self.env['stock.picking'] pickings_not_to_do = self.env['stock.picking'] for line in self.immediate_transfer_line_ids: if line.to_immediate is True: + line.picking_id.send_mail_bills() + line.picking_id.send_koli_to_so() pickings_to_do |= line.picking_id else: pickings_not_to_do |= line.picking_id for picking in pickings_to_do: - picking.send_mail_bills() - # If still in draft => confirm and assign if picking.state == 'draft': picking.action_confirm() if picking.state != 'assigned': picking.action_assign() if picking.state != 'assigned': raise UserError(_("Could not reserve all requested products. Please use the 'Mark as Todo' button to handle the reservation manually.")) + for move in picking.move_lines.filtered(lambda m: m.state not in ['done', 'cancel']): for move_line in move.move_line_ids: move_line.qty_done = move_line.product_uom_qty @@ -33,4 +33,6 @@ class StockImmediateTransfer(models.TransientModel): pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate) pickings_to_validate = pickings_to_validate - pickings_not_to_do return pickings_to_validate.with_context(skip_immediate=True).button_validate() + return True + diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 02ce819f..f3af00d9 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -125,7 +125,6 @@ class StockPicking(models.Model): ], 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", copy=False) - source_koli_id = fields.Many2one('stock.picking', string="Source Koli") @api.model def _compute_dokumen_tanda_terima(self): @@ -170,45 +169,17 @@ 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 - + total_so_koli = fields.Integer(compute='_compute_total_so_koli', string="Total SO Koli") - @api.depends('total_so_koli') # Sesuaikan dengan field yang relevan + @api.depends('total_so_koli') 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)]) + picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '!=', 'delivered')]) - @api.depends('total_koli') # Sesuaikan dengan field yang relevan + @api.depends('total_koli') def _compute_total_koli(self): for picking in self: picking.total_koli = self.env['scan.koli'].search_count([('picking_id', '=', picking.id)]) @@ -425,7 +396,7 @@ class StockPicking(models.Model): "name": order_line.product_id.name, "description": order_line.name, "value": order_line.price_unit, - "quantity": move_line.qty_done, # Menggunakan qty_done dari move_line + "quantity": move_line.qty_done, "weight": order_line.weight }) @@ -820,7 +791,7 @@ class StockPicking(models.Model): raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): - if self.total_koli != self.total_so_koli: + 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': @@ -870,8 +841,7 @@ 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() + # 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() @@ -890,21 +860,29 @@ class StockPicking(models.Model): 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 - }) - - + if picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name: + 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: + self.env['sales.order.koli'].create({ + 'sale_order_id': picking.sale_id.id, + 'picking_id': picking.id, + 'koli_id': koli_line.id + }) + + if picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name: + for koli_line in picking.scan_koli_lines: + existing_koli = self.env['sales.order.koli'].search([ + ('sale_order_id', '=', picking.sale_id.id), + ('koli_id', '=', koli_line.koli_id.koli_id.id) + ], limit=1) + + existing_koli.state = 'delivered' def check_qty_done_stock(self): for line in self.move_line_ids_without_package: @@ -981,62 +959,11 @@ 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 def create(self, vals): self._use_faktur(vals) - if vals.get('picking_type_code') == 'incoming' and vals.get('location_dest_id') == 58: - if 'name' in vals and vals['name'].startswith('BU/IN/'): - vals['name'] = vals['name'].replace('BU/IN/', 'BU/INPUT/', 1) - - if vals.get('picking_type_code') == 'internal' and vals.get('location_id') == 58: - if 'name' in vals and vals['name'].startswith('BU/INT'): - new_name = vals['name'].replace('BU/INT', 'BU/IN', 1) - # Periksa apakah nama sudah ada - 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 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 @@ -1367,6 +1294,7 @@ class CheckKoli(models.Model): copy=False, ) koli = fields.Char(string='Koli') + reserved_id = fields.Many2one('stock.picking', string='Reserved Picking') class ScanKoli(models.Model): _name = 'scan.koli' @@ -1388,6 +1316,35 @@ class ScanKoli(models.Model): compute="_compute_scan_koli_progress" ) + def unlink(self): + for scan in self: + koli = scan.koli_id.koli_id + if koli: + # Hapus reserved_id saat scan dihapus + koli.reserved_id = False + + # Ambil semua scan koli yang masih ada dan memiliki picking_id yang sama + remaining_scans = self.env['scan.koli'].search([ + ('id', '!=', scan.id), # Kecuali scan yang sedang dihapus + ('koli_id.picking_id', '=', koli.picking_id.id) + ]) + + # Jika tidak ada scan lain yang memiliki picking_id yang sama, hapus linked_out_picking_id + if not remaining_scans: + koli.picking_id.linked_out_picking_id = False + + return super(ScanKoli, self).unlink() + + @api.onchange('koli_id','scan_koli_progress') + def onchange_koli_id(self): + if not self.koli_id: + return + + for scan in self: + if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: + scan.koli_id.koli_id.reserved_id = scan.picking_id.id.origin + scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin + def _compute_scan_koli_progress(self): """ Menghitung progres scan koli dalam format 'X/Y' """ for scan in self: @@ -1397,18 +1354,12 @@ class ScanKoli(models.Model): 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: + scan.koli_id.koli_id.reserved_id = scan.picking_id.id + scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id 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!")) @@ -1418,9 +1369,20 @@ class ScanKoli(models.Model): 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 - - # Cek apakah source_koli ditemukan + source_koli_so = self.picking_id.group_id.id + source_koli = self.koli_id.picking_id.group_id.id + if source_koli_so != source_koli: raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + + @api.onchange('koli_id') + def _onchange_koliii(self): + if self.koli_id and self.picking_id: + existing_koli = self.env['scan.koli'].search([ + ('picking_id', '=', self.picking_id.id), + ('koli_id', '=', self.koli_id.id), + ('id', '!=', self.id.origin) # Hindari validasi saat edit data + ]) + if existing_koli: + self.koli_id = False # Reset field koli_id agar pengguna tidak bisa memilihnya + raise UserError(f"Koli {existing_koli.koli_id.name} sudah dipindai dalam picking ini!") \ No newline at end of file diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 877208b0..4d31b072 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -401,6 +401,8 @@ + + diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 2a11459c..1b3406ec 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -73,10 +73,8 @@ - - @@ -219,7 +217,7 @@ scan.koli - + @@ -231,6 +229,7 @@ + -- cgit v1.2.3 From 0174a19b631d67a70805de45b252cdcf1c562fb6 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Feb 2025 10:28:15 +0700 Subject: push wms --- indoteknik_custom/models/stock_immediate_transfer.py | 6 ++++-- indoteknik_custom/models/stock_picking.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index ec00df7b..8724c567 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -9,14 +9,16 @@ class StockImmediateTransfer(models.TransientModel): pickings_not_to_do = self.env['stock.picking'] for line in self.immediate_transfer_line_ids: + line.picking_id.send_mail_bills() + line.picking_id.send_koli_to_so() if line.to_immediate is True: - line.picking_id.send_mail_bills() - line.picking_id.send_koli_to_so() pickings_to_do |= line.picking_id else: pickings_not_to_do |= line.picking_id for picking in pickings_to_do: + picking.send_mail_bills() + picking.send_koli_to_so() if picking.state == 'draft': picking.action_confirm() if picking.state != 'assigned': diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f3af00d9..f359a2fb 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -848,6 +848,7 @@ class StockPicking(models.Model): self.calculate_line_no() self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' + self.send_koli_to_so() return res -- cgit v1.2.3 From e94dbdf4418c686ec4e8fdab41d4f05e5284fbfb Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 26 Feb 2025 13:41:50 +0700 Subject: push wms --- .../models/stock_immediate_transfer.py | 4 +- indoteknik_custom/models/stock_picking.py | 104 +++++++++++++-------- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index 8724c567..35c17192 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -17,8 +17,8 @@ class StockImmediateTransfer(models.TransientModel): pickings_not_to_do |= line.picking_id for picking in pickings_to_do: - picking.send_mail_bills() - picking.send_koli_to_so() + # picking.send_mail_bills() + # picking.send_koli_to_so() if picking.state == 'draft': picking.action_confirm() if picking.state != 'assigned': diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f359a2fb..bbd9043d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -4,6 +4,7 @@ from odoo.tools.float_utils import float_is_zero from datetime import timedelta, datetime from itertools import groupby import pytz, requests, json, requests +from collections import defaultdict from dateutil import parser import datetime import hmac @@ -1318,72 +1319,97 @@ class ScanKoli(models.Model): ) def unlink(self): + picking_ids = set(self.mapped('picking_id.id')) # Tangkap picking_id sebelum hapus scan.koli + for scan in self: koli = scan.koli_id.koli_id if koli: - # Hapus reserved_id saat scan dihapus koli.reserved_id = False - # Ambil semua scan koli yang masih ada dan memiliki picking_id yang sama - remaining_scans = self.env['scan.koli'].search([ - ('id', '!=', scan.id), # Kecuali scan yang sedang dihapus + # Jika tidak ada scan.koli lain untuk picking_id yang sama, reset linked_out_picking_id + if not self.env['scan.koli'].search_count([ + ('id', '!=', scan.id), ('koli_id.picking_id', '=', koli.picking_id.id) - ]) - - # Jika tidak ada scan lain yang memiliki picking_id yang sama, hapus linked_out_picking_id - if not remaining_scans: + ]): koli.picking_id.linked_out_picking_id = False - return super(ScanKoli, self).unlink() + result = super(ScanKoli, self).unlink() # Hapus scan.koli + + # Reset qty_done jika semua scan.koli untuk picking_id tersebut telah dihapus + for picking_id in picking_ids: + self._reset_qty_done_if_no_scan(picking_id) + + return result - @api.onchange('koli_id','scan_koli_progress') + def _reset_qty_done_if_no_scan(self, picking_id): + """Set qty_done ke 0 hanya jika tidak ada scan.koli tersisa untuk picking_id tersebut.""" + remaining_scans = self.env['scan.koli'].search_count([('picking_id', '=', picking_id)]) + + if remaining_scans == 0: + picking = self.env['stock.picking'].browse(picking_id) + picking.move_line_ids_without_package.write({'qty_done': 0}) + picking.message_post(body=f"⚠️ qty_done direset ke 0 untuk Picking {picking.name} karena tidak ada scan.koli yang tersisa.") + + return remaining_scans + + @api.onchange('koli_id', 'scan_koli_progress') def onchange_koli_id(self): - if not self.koli_id: - return - - for scan in self: - if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: + for scan in self.filtered('koli_id'): + if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: scan.koli_id.koli_id.reserved_id = scan.picking_id.id.origin scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin + @api.depends('picking_id') 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" + scan.scan_koli_progress = f"{scan.picking_id.scan_koli_lines.ids.index(scan.id) + 1}/{total_so_koli}" if total_so_koli else "0/0" @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: scan.koli_id.koli_id.reserved_id = scan.picking_id.id scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id - 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!")) + if len(scan.picking_id.scan_koli_lines) != scan.picking_id.total_so_koli: + raise UserError("Jumlah scan koli tidak sama dengan total SO koli!") @api.onchange('koli_id') def _onchange_koli_id(self): + for scan in self.filtered('koli_id'): + if scan.picking_id.group_id.id != scan.koli_id.picking_id.group_id.id: + raise UserError('Koli tidak sesuai, pastikan picking terkait benar!') + + @api.onchange('koli_id') + def _onchange_koliii(self): + for scan in self.filtered('koli_id'): + if self.env['scan.koli'].search_count([ + ('picking_id', '=', scan.picking_id.id), + ('koli_id', '=', scan.koli_id.id), + ('id', '!=', scan.id.origin) + ]): + scan.koli_id = False + raise UserError(f"Koli {scan.koli_id.name} sudah dipindai dalam picking ini!") + + @api.constrains('koli_id') + def _send_product_from_koli_id(self): if not self.koli_id: return - - source_koli_so = self.picking_id.group_id.id - source_koli = self.koli_id.picking_id.group_id.id - if source_koli_so != source_koli: - raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + koli_count_by_picking = defaultdict(int) + for scan in self: + koli_count_by_picking[scan.koli_id.picking_id.id] += 1 # Hitung jumlah koli per picking - @api.onchange('koli_id') - def _onchange_koliii(self): - if self.koli_id and self.picking_id: - existing_koli = self.env['scan.koli'].search([ - ('picking_id', '=', self.picking_id.id), - ('koli_id', '=', self.koli_id.id), - ('id', '!=', self.id.origin) # Hindari validasi saat edit data - ]) - if existing_koli: - self.koli_id = False # Reset field koli_id agar pengguna tidak bisa memilihnya - raise UserError(f"Koli {existing_koli.koli_id.name} sudah dipindai dalam picking ini!") \ No newline at end of file + for picking_id, total_koli in koli_count_by_picking.items(): + picking = self.env['stock.picking'].browse(picking_id) + + if total_koli == picking.quantity_koli: + # Ambil stock moves dari BU/PICK dan BU/OUT berdasarkan picking_id + pick_moves = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) + out_moves = self.env['stock.move.line'].search([('picking_id', '=', picking.linked_out_picking_id.id)]) + + # Sesuaikan product_id di BU/OUT dengan BU/PICK + for pick_move in pick_moves: + corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id) + if corresponding_out_move: + corresponding_out_move.qty_done = corresponding_out_move.product_uom_qty # Update qty_done \ No newline at end of file -- cgit v1.2.3 From 20a56a76c519eba82f70ea1443e272ac64797dd9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 3 Mar 2025 16:51:05 +0700 Subject: push --- .../models/stock_backorder_confirmation.py | 2 +- indoteknik_custom/models/stock_picking.py | 195 +++++++++++++++------ indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/stock_picking.xml | 49 ++++-- 4 files changed, 181 insertions(+), 66 deletions(-) diff --git a/indoteknik_custom/models/stock_backorder_confirmation.py b/indoteknik_custom/models/stock_backorder_confirmation.py index f4da4cb5..d8a41f54 100644 --- a/indoteknik_custom/models/stock_backorder_confirmation.py +++ b/indoteknik_custom/models/stock_backorder_confirmation.py @@ -9,7 +9,7 @@ class StockBackorderConfirmation(models.TransientModel): pickings_not_to_do = self.env['stock.picking'] for line in self.backorder_confirmation_line_ids: line.picking_id.send_mail_bills() - line.picking_id.send_koli_to_so() + # line.picking_id.send_koli_to_so() if line.to_backorder is True: pickings_to_do |= line.picking_id else: diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index bbd9043d..dfc33caa 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1,10 +1,10 @@ from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools.float_utils import float_is_zero +from collections import defaultdict from datetime import timedelta, datetime from itertools import groupby import pytz, requests, json, requests -from collections import defaultdict from dateutil import parser import datetime import hmac @@ -127,6 +127,8 @@ class StockPicking(models.Model): notee = fields.Text(string="Note") quantity_koli = fields.Float(string="Quantity Koli", copy=False) + + @api.model def _compute_dokumen_tanda_terima(self): for picking in self: @@ -178,7 +180,10 @@ class StockPicking(models.Model): @api.depends('total_so_koli') def _compute_total_so_koli(self): for picking in self: - picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '!=', 'delivered')]) + if picking.state == 'done': + picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '=', 'delivered')]) + else: + picking.total_so_koli = self.env['sales.order.koli'].search_count([('picking_id.linked_out_picking_id', '=', picking.id), ('state', '!=', 'delivered')]) @api.depends('total_koli') def _compute_total_koli(self): @@ -190,6 +195,24 @@ class StockPicking(models.Model): for picking in self: picking.total_koli_display = f"{picking.total_koli} / {picking.total_so_koli}" + @api.constrains('quantity_koli') + def _constrains_quantity_koli(self): + for picking in self: + if not picking.linked_out_picking_id: + so_koli = self.env['sales.order.koli'].search([('picking_id', '=', picking.id)]) + + if so_koli: + so_koli.unlink() + + for rec in picking.check_koli_lines: + self.env['sales.order.koli'].create({ + 'sale_order_id': picking.sale_id.id, + 'picking_id': picking.id, + 'koli_id': rec.id, + }) + else: + raise UserError('Tidak Bisa Mengubah Quantity Koli Karena Koli Dari Picking Ini Sudah Dipakai Di BU/OUT!') + @api.onchange('quantity_koli') def _onchange_quantity_koli(self): self.check_koli_lines = [(5, 0, 0)] @@ -850,6 +873,31 @@ class StockPicking(models.Model): self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' self.send_koli_to_so() + if not self.env.context.get('skip_koli_check'): + for picking in self: + if picking.sale_id: + all_koli_ids = picking.sale_id.koli_lines.filtered(lambda k: k.state != 'delivered').ids + scanned_koli_ids = picking.scan_koli_lines.mapped('koli_id.id') + + missing_koli_ids = set(all_koli_ids) - set(scanned_koli_ids) + + if len(missing_koli_ids) > 0 and picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name: + missing_koli_names = picking.sale_id.koli_lines.filtered(lambda k: k.id in missing_koli_ids and k.state != 'delivered').mapped('display_name') + missing_koli_list = "\n".join(f"- {name}" for name in missing_koli_names) + + # Buat wizard modal warning + wizard = self.env['warning.modal.wizard'].create({ + 'message': f"Berikut Koli yang belum discan:\n{missing_koli_list}", + 'picking_id': picking.id, + }) + + return { + 'type': 'ir.actions.act_window', + 'res_model': 'warning.modal.wizard', + 'view_mode': 'form', + 'res_id': wizard.id, + 'target': 'new', + } return res @@ -876,15 +924,16 @@ class StockPicking(models.Model): 'picking_id': picking.id, 'koli_id': koli_line.id }) - + if picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name: - for koli_line in picking.scan_koli_lines: - existing_koli = self.env['sales.order.koli'].search([ - ('sale_order_id', '=', picking.sale_id.id), - ('koli_id', '=', koli_line.koli_id.koli_id.id) - ], limit=1) - - existing_koli.state = 'delivered' + if picking.state == 'done': + for koli_line in picking.scan_koli_lines: + existing_koli = self.env['sales.order.koli'].search([ + ('sale_order_id', '=', picking.sale_id.id), + ('koli_id', '=', koli_line.koli_id.koli_id.id) + ], limit=1) + + existing_koli.state = 'delivered' def check_qty_done_stock(self): for line in self.move_line_ids_without_package: @@ -1319,78 +1368,83 @@ class ScanKoli(models.Model): ) def unlink(self): - picking_ids = set(self.mapped('picking_id.id')) # Tangkap picking_id sebelum hapus scan.koli - + picking_ids = set(self.mapped('koli_id.picking_id.id')) # Ambil semua picking_id yang terpengaruh for scan in self: koli = scan.koli_id.koli_id if koli: + # Hapus reserved_id saat scan dihapus koli.reserved_id = False - # Jika tidak ada scan.koli lain untuk picking_id yang sama, reset linked_out_picking_id - if not self.env['scan.koli'].search_count([ - ('id', '!=', scan.id), - ('koli_id.picking_id', '=', koli.picking_id.id) - ]): - koli.picking_id.linked_out_picking_id = False - - result = super(ScanKoli, self).unlink() # Hapus scan.koli - - # Reset qty_done jika semua scan.koli untuk picking_id tersebut telah dihapus + # Periksa ulang apakah masih ada scan.koli yang tersisa untuk setiap picking_id for picking_id in picking_ids: - self._reset_qty_done_if_no_scan(picking_id) + remaining_scans = self.env['sales.order.koli'].search_count([ + ('koli_id.picking_id', '=', picking_id) + ]) - return result + delete_koli = len(self.filtered(lambda rec: rec.koli_id.picking_id.id == picking_id)) - def _reset_qty_done_if_no_scan(self, picking_id): - """Set qty_done ke 0 hanya jika tidak ada scan.koli tersisa untuk picking_id tersebut.""" - remaining_scans = self.env['scan.koli'].search_count([('picking_id', '=', picking_id)]) - if remaining_scans == 0: - picking = self.env['stock.picking'].browse(picking_id) - picking.move_line_ids_without_package.write({'qty_done': 0}) - picking.message_post(body=f"⚠️ qty_done direset ke 0 untuk Picking {picking.name} karena tidak ada scan.koli yang tersisa.") + # Jika tidak ada scan.koli lain yang tersisa, set linked_out_picking_id ke False + if remaining_scans == delete_koli: + picking = self.env['stock.picking'].browse(picking_id) + picking.linked_out_picking_id = False + else: + raise UserError(_("Tidak dapat menghapus scan koli, karena masih ada scan koli lain yang tersisa untuk picking ini.")) + + for picking_id in picking_ids: + self._reset_qty_done_if_no_scan(picking_id) + + # self.check_koli_not_balance() - return remaining_scans + return super(ScanKoli, self).unlink() - @api.onchange('koli_id', 'scan_koli_progress') + @api.onchange('koli_id','scan_koli_progress') def onchange_koli_id(self): - for scan in self.filtered('koli_id'): - if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: + if not self.koli_id: + return + + for scan in self: + if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id: scan.koli_id.koli_id.reserved_id = scan.picking_id.id.origin scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin - @api.depends('picking_id') 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.picking_id.scan_koli_lines.ids.index(scan.id) + 1}/{total_so_koli}" if total_so_koli else "0/0" + scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0" @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): - for scan in self: + """ Validasi jika jumlah scan koli melebihi total SO koli """ + for scan in self.picking_id.scan_koli_lines: scan.koli_id.koli_id.reserved_id = scan.picking_id.id scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id - if len(scan.picking_id.scan_koli_lines) != scan.picking_id.total_so_koli: - raise UserError("Jumlah scan koli tidak sama dengan total SO koli!") + + total_scans = len(self.picking_id.scan_koli_lines) + if total_scans != self.picking_id.total_so_koli: + raise UserError(_("Jumlah scan koli tidak sama dengan total SO koli!")) + + # def check_koli_not_balance(self): + # for scan in self: + # total_scancs = self.env['scan.koli'].search_count([('picking_id', '=', scan.picking_id.id), ('id', '!=', scan.id)]) + # if total_scancs != scan.picking_id.total_so_koli: + # raise UserError(_("Jumlah scan koli tidak sama dengan total SO koli!")) @api.onchange('koli_id') def _onchange_koli_id(self): - for scan in self.filtered('koli_id'): - if scan.picking_id.group_id.id != scan.koli_id.picking_id.group_id.id: - raise UserError('Koli tidak sesuai, pastikan picking terkait benar!') - - @api.onchange('koli_id') - def _onchange_koliii(self): - for scan in self.filtered('koli_id'): - if self.env['scan.koli'].search_count([ - ('picking_id', '=', scan.picking_id.id), - ('koli_id', '=', scan.koli_id.id), - ('id', '!=', scan.id.origin) - ]): - scan.koli_id = False - raise UserError(f"Koli {scan.koli_id.name} sudah dipindai dalam picking ini!") + if not self.koli_id: + return + source_koli_so = self.picking_id.group_id.id + source_koli = self.koli_id.picking_id.group_id.id + + if source_koli_so != source_koli: + raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + @api.constrains('koli_id') def _send_product_from_koli_id(self): if not self.koli_id: @@ -1412,4 +1466,35 @@ class ScanKoli(models.Model): for pick_move in pick_moves: corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id) if corresponding_out_move: - corresponding_out_move.qty_done = corresponding_out_move.product_uom_qty # Update qty_done \ No newline at end of file + corresponding_out_move.qty_done += pick_move.qty_done + + def _reset_qty_done_if_no_scan(self, picking_id): + """Set qty_done ke 0 hanya jika tidak ada scan.koli tersisa untuk picking_id tersebut.""" + product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) + + for move in product_bu_pick: + product_bu_out = self.env['stock.move.line'].search([('picking_id', '=', self.picking_id.id), ('product_id', '=', move.product_id.id)]) + for bu_out in product_bu_out: + bu_out.qty_done -= move.qty_done + # if remaining_scans == 0: + # picking = self.env['stock.picking'].browse(picking_id) + # picking.move_line_ids_without_package.write({'qty_done': 0}) + # picking.message_post(body=f"⚠ qty_done direset ke 0 untuk Picking {picking.name} karena tidak ada scan.koli yang tersisa.") + + # return remaining_scans + +class WarningModalWizard(models.TransientModel): + _name = 'warning.modal.wizard' + _description = 'Peringatan Koli Belum Diperiksa' + + name = fields.Char(default="⚠️ Perhatian!") + message = fields.Text() + picking_id = fields.Many2one('stock.picking') + + def action_continue(self): + """Lanjutkan validasi setelah menutup wizard""" + if self.picking_id: + return self.picking_id.with_context(skip_koli_check=True).button_validate() + return {'type': 'ir.actions.act_window_close'} + + diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 3040ff2f..83de8bda 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -157,6 +157,7 @@ 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_warning_modal_wizard,access.warning.modal.wizard,model_warning_modal_wizard,,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/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 1b3406ec..016fbf17 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -72,9 +72,9 @@ - - - + + + @@ -147,7 +147,7 @@ - + @@ -195,7 +195,7 @@ - + @@ -217,7 +217,7 @@ scan.koli - + @@ -239,9 +239,9 @@ check.product - - - + + + @@ -278,6 +278,35 @@ - + + + + warning.modal.wizard.form + warning.modal.wizard + +
+ +
+

⚠️ Perhatian!

+
+ + + +
+
+
+
+
+
+ + + Peringatan Koli + warning.modal.wizard + form + + new + \ No newline at end of file -- cgit v1.2.3 From 660913a45a1efe08f308d405e1011efc9744c553 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 5 Mar 2025 10:14:34 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index dfc33caa..df91d451 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -815,6 +815,9 @@ class StockPicking(models.Model): raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): + if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + 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)) -- cgit v1.2.3 From ad19154ae49ec5bc1178006344baf104154167bf Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 6 Mar 2025 15:11:57 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 18 ++++++++++++++++++ indoteknik_custom/models/stock_picking_return.py | 13 ++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0699295f..327389cd 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -852,6 +852,12 @@ class StockPicking(models.Model): if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) + + if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name: + raise UserError(_("Tidak ada koli! Harap periksa kembali.")) + 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)) @@ -1405,6 +1411,18 @@ class ScanKoli(models.Model): compute="_compute_scan_koli_progress" ) + @api.constrains('picking_id', 'koli_id') + def _check_duplicate_koli(self): + for record in self: + if record.koli_id: + existing_koli = self.search([ + ('picking_id', '=', record.picking_id.id), + ('koli_id', '=', record.koli_id.id), + ('id', '!=', record.id) # Exclude current record + ]) + if existing_koli: + raise ValidationError(f"⚠️ Koli '{record.koli_id.display_name}' sudah discan untuk picking ini!") + def unlink(self): picking_ids = set(self.mapped('koli_id.picking_id.id')) # Ambil semua picking_id yang terpengaruh for scan in self: diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index d4347235..a683d80e 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -24,4 +24,15 @@ class ReturnPicking(models.TransientModel): # if not stock_picking.approval_return_status == 'approved' and purchase.invoice_ids: # raise UserError('Harus Approval Accounting AP untuk melakukan Retur') - return res \ No newline at end of file + return res + +class ReturnPickingLine(models.TransientModel): + _inherit = 'stock.return.picking.line' + + @api.onchange('quantity') + def _onchange_quantity(self): + for rec in self: + qty_done = rec.move_id.quantity_done + + if rec.quantity > qty_done: + raise UserError(f"Quantity yang Anda masukkan tidak boleh melebihi quantity done yaitu: {qty_done}") \ No newline at end of file -- cgit v1.2.3 From ffcad6c52773063a05a91721b1203975a5a6359e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 10 Mar 2025 14:33:43 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 3 --- indoteknik_custom/security/ir.model.access.csv | 3 --- 2 files changed, 6 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 7b70c2b9..c5b6387d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -20,8 +20,6 @@ _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" - - class StockPicking(models.Model): _inherit = 'stock.picking' @@ -245,7 +243,6 @@ class StockPicking(models.Model): query = "SELECT update_sequance_stock_picking();" self.env.cr.execute(query) - @api.depends('estimated_ready_ship_date', 'state') def _callculate_sequance(self): for record in self: diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 4e580d03..57e02363 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -158,13 +158,10 @@ 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 -<<<<<<< HEAD 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_warning_modal_wizard,access.warning.modal.wizard,model_warning_modal_wizard,,1,1,1,1 -======= ->>>>>>> odoo-backup 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 access_reject_reason_wizard,reject.reason.wizard,model_reject_reason_wizard,,1,1,1,0 -- cgit v1.2.3 From beb653de0340d270f2d56dd7b7145c3552e91ab4 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Mar 2025 09:37:33 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 42 +++++++++++++++++++++++++- indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/stock_picking.xml | 15 ++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index c5b6387d..c3febc02 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -23,9 +23,10 @@ _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1l class StockPicking(models.Model): _inherit = 'stock.picking' + _order = 'final_seq ASC' + konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True) scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True) check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True) - _order = 'final_seq ASC' check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True) barcode_product_lines = fields.One2many('barcode.product', 'picking_id', string='Barcode Product', auto_join=True) @@ -1585,6 +1586,29 @@ class ScanKoli(models.Model): compute="_compute_scan_koli_progress" ) + @api.onchange('koli_id') + def _onchange_koli_compare_with_konfirm_koli(self): + if not self.koli_id: + return + + # Pastikan konfirm_koli_lines tidak kosong + if not self.picking_id.konfirm_koli_lines: + raise UserError(_('Konfirm Koli Harus Diisi!')) + + # Ambil origin picking dari koli yang dipilih + koli_picking = self.koli_id.picking_id._origin + + # Kumpulkan semua origin picking dari konfirm koli lines + konfirm_pick_ids = [ + line.pick_id._origin + for line in self.picking_id.konfirm_koli_lines + if line.pick_id + ] + + # Validasi apakah koli_picking ada dalam daftar konfirmasi + if koli_picking not in konfirm_pick_ids: + raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + @api.constrains('picking_id', 'koli_id') def _check_duplicate_koli(self): for record in self: @@ -1713,6 +1737,22 @@ class ScanKoli(models.Model): # return remaining_scans +class KonfirmKoli(models.Model): + _name = 'konfirm.koli' + _description = 'Konfirm Koli' + _order = 'picking_id, id' + _rec_name = 'pick_id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + pick_id = fields.Many2one('stock.picking', string='Pick') + class WarningModalWizard(models.TransientModel): _name = 'warning.modal.wizard' _description = 'Peringatan Koli Belum Diperiksa' diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 57e02363..d3905d41 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -155,6 +155,7 @@ access_vendor_sla,access.vendor_sla,model_vendor_sla,,1,1,1,1 access_check_product,access.check.product,model_check_product,,1,1,1,1 access_check_koli,access.check.koli,model_check_koli,,1,1,1,1 access_scan_koli,access.scan.koli,model_scan_koli,,1,1,1,1 +access_konfirm_koli,access.konfirm.koli,model_konfirm_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 diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 144ed820..67593b5b 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -208,7 +208,10 @@ - + + + + @@ -227,6 +230,16 @@
+ + konfirm.koli.tree + konfirm.koli + + + + + + + check.koli.tree check.koli -- cgit v1.2.3 From d094b9634a6d32549655c99ff370e45fb568f11d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 25 Mar 2025 11:06:32 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index c3febc02..0e425f68 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -937,9 +937,12 @@ class StockPicking(models.Model): raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): + if len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali.")) + if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) - + if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) @@ -1593,7 +1596,7 @@ class ScanKoli(models.Model): # Pastikan konfirm_koli_lines tidak kosong if not self.picking_id.konfirm_koli_lines: - raise UserError(_('Konfirm Koli Harus Diisi!')) + raise UserError(_('Mapping Koli Harus Diisi!')) # Ambil origin picking dari koli yang dipilih koli_picking = self.koli_id.picking_id._origin @@ -1607,7 +1610,7 @@ class ScanKoli(models.Model): # Validasi apakah koli_picking ada dalam daftar konfirmasi if koli_picking not in konfirm_pick_ids: - raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!')) + raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!')) @api.constrains('picking_id', 'koli_id') def _check_duplicate_koli(self): -- cgit v1.2.3 From ccd98307c3b48b25bbbb053caa2dba0cce5117d1 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 9 Apr 2025 17:07:30 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 36 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0e425f68..be033b39 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -943,8 +943,8 @@ class StockPicking(models.Model): if len(self.scan_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) - if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': - raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) + # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing': + # raise UserError(_("Isi Driver Departure Date dulu sebelum validate")) if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada koli! Harap periksa kembali.")) @@ -1594,21 +1594,17 @@ class ScanKoli(models.Model): if not self.koli_id: return - # Pastikan konfirm_koli_lines tidak kosong if not self.picking_id.konfirm_koli_lines: raise UserError(_('Mapping Koli Harus Diisi!')) - # Ambil origin picking dari koli yang dipilih koli_picking = self.koli_id.picking_id._origin - # Kumpulkan semua origin picking dari konfirm koli lines konfirm_pick_ids = [ line.pick_id._origin for line in self.picking_id.konfirm_koli_lines if line.pick_id ] - # Validasi apakah koli_picking ada dalam daftar konfirmasi if koli_picking not in konfirm_pick_ids: raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!')) @@ -1619,20 +1615,18 @@ class ScanKoli(models.Model): existing_koli = self.search([ ('picking_id', '=', record.picking_id.id), ('koli_id', '=', record.koli_id.id), - ('id', '!=', record.id) # Exclude current record + ('id', '!=', record.id) ]) if existing_koli: raise ValidationError(f"⚠️ Koli '{record.koli_id.display_name}' sudah discan untuk picking ini!") def unlink(self): - picking_ids = set(self.mapped('koli_id.picking_id.id')) # Ambil semua picking_id yang terpengaruh + picking_ids = set(self.mapped('koli_id.picking_id.id')) for scan in self: koli = scan.koli_id.koli_id if koli: - # Hapus reserved_id saat scan dihapus koli.reserved_id = False - # Periksa ulang apakah masih ada scan.koli yang tersisa untuk setiap picking_id for picking_id in picking_ids: remaining_scans = self.env['sales.order.koli'].search_count([ ('koli_id.picking_id', '=', picking_id) @@ -1640,8 +1634,6 @@ class ScanKoli(models.Model): delete_koli = len(self.filtered(lambda rec: rec.koli_id.picking_id.id == picking_id)) - - # Jika tidak ada scan.koli lain yang tersisa, set linked_out_picking_id ke False if remaining_scans == delete_koli: picking = self.env['stock.picking'].browse(picking_id) picking.linked_out_picking_id = False @@ -1666,7 +1658,6 @@ class ScanKoli(models.Model): scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin 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') @@ -1676,7 +1667,6 @@ class ScanKoli(models.Model): @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.picking_id.scan_koli_lines: scan.koli_id.koli_id.reserved_id = scan.picking_id.id scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id @@ -1709,24 +1699,21 @@ class ScanKoli(models.Model): koli_count_by_picking = defaultdict(int) for scan in self: - koli_count_by_picking[scan.koli_id.picking_id.id] += 1 # Hitung jumlah koli per picking + koli_count_by_picking[scan.koli_id.picking_id.id] += 1 for picking_id, total_koli in koli_count_by_picking.items(): picking = self.env['stock.picking'].browse(picking_id) if total_koli == picking.quantity_koli: - # Ambil stock moves dari BU/PICK dan BU/OUT berdasarkan picking_id pick_moves = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) out_moves = self.env['stock.move.line'].search([('picking_id', '=', picking.linked_out_picking_id.id)]) - # Sesuaikan product_id di BU/OUT dengan BU/PICK for pick_move in pick_moves: corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id) if corresponding_out_move: corresponding_out_move.qty_done += pick_move.qty_done def _reset_qty_done_if_no_scan(self, picking_id): - """Set qty_done ke 0 hanya jika tidak ada scan.koli tersisa untuk picking_id tersebut.""" product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) for move in product_bu_pick: @@ -1756,6 +1743,18 @@ class KonfirmKoli(models.Model): ) pick_id = fields.Many2one('stock.picking', string='Pick') + @api.constrains('pick_id') + def _check_duplicate_pick_id(self): + for rec in self: + exist = self.search([ + ('pick_id', '=', rec.pick_id.id), + ('picking_id', '=', rec.picking_id.id), + ('id', '!=', rec.id), + ]) + + if exist: + raise UserError(f"⚠️ '{rec.pick_id.display_name}' sudah discan untuk picking ini!") + class WarningModalWizard(models.TransientModel): _name = 'warning.modal.wizard' _description = 'Peringatan Koli Belum Diperiksa' @@ -1765,7 +1764,6 @@ class WarningModalWizard(models.TransientModel): picking_id = fields.Many2one('stock.picking') def action_continue(self): - """Lanjutkan validasi setelah menutup wizard""" if self.picking_id: return self.picking_id.with_context(skip_koli_check=True).button_validate() return {'type': 'ir.actions.act_window_close'} -- cgit v1.2.3 From 00c69ce93bdb0071cd563be855857d2137115868 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 10 Apr 2025 13:36:30 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 35 ++++++++++++++++++++++++++++--- indoteknik_custom/views/stock_picking.xml | 3 ++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index be033b39..3aa18233 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -195,6 +195,12 @@ class StockPicking(models.Model): # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) final_seq = fields.Float(string='Remaining Time') + @api.constrains('scan_koli_lines') + def _constrains_scan_koli_lines(self): + now = datetime.datetime.utcnow() + for picking in self: + if len(picking.scan_koli_lines) > 0: + picking.driver_departure_date = now @api.depends('total_so_koli') def _compute_total_so_koli(self): @@ -1568,6 +1574,19 @@ class CheckKoli(models.Model): ) koli = fields.Char(string='Koli') reserved_id = fields.Many2one('stock.picking', string='Reserved Picking') + check_koli_progress = fields.Char( + string="Progress Check Koli" + ) + + @api.constrains('koli') + def _check_koli_progress(self): + for check in self: + if check.picking_id: + all_checks = self.env['check.koli'].search([('picking_id', '=', check.picking_id.id)], order='id') + if all_checks: + check_index = list(all_checks).index(check) + 1 # Nomor urut check + total_so_koli = len(all_checks) + check.check_koli_progress = f"{check_index}/{total_so_koli}" if total_so_koli else "0/0" class ScanKoli(models.Model): _name = 'scan.koli' @@ -1589,6 +1608,15 @@ class ScanKoli(models.Model): compute="_compute_scan_koli_progress" ) + def _compute_scan_koli_progress(self): + for scan in self: + if scan.picking_id: + all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') + if all_scans: + 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.onchange('koli_id') def _onchange_koli_compare_with_konfirm_koli(self): if not self.koli_id: @@ -1661,9 +1689,10 @@ class ScanKoli(models.Model): 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" + if all_scans: + 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.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 67593b5b..d6850b2f 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -244,9 +244,10 @@ check.koli.tree check.koli - + + -- cgit v1.2.3