From 3d672f12eecd77af9a805cebd8ce3a7083957a1f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 16 Jan 2025 10:30:17 +0700 Subject: wms push --- indoteknik_custom/models/stock_picking.py | 234 ++++++++++++++++++++++++------ 1 file changed, 186 insertions(+), 48 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cd330aeb..05a7ee4a 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -16,7 +16,8 @@ _logger = logging.getLogger(__name__) class StockPicking(models.Model): _inherit = 'stock.picking' - # check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', 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') account_id = fields.Many2one('account.account', string='Account') efaktur_id = fields.Many2one('vit.efaktur', string='Faktur Pajak') @@ -176,7 +177,8 @@ class StockPicking(models.Model): pickings = self.env['stock.picking'].search([ ('picking_type_code', '=', 'outgoing'), ('state', '=', 'done'), - ('carrier_id', '=', 9) + ('carrier_id', '=', 9), + ('lalamove_order_id', '!=', False) ]) for picking in pickings: try: @@ -810,7 +812,24 @@ class StockPicking(models.Model): self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' return res - + + + def check_qty_done_stock(self): + for line in self.move_line_ids_without_package: + def check_qty_per_inventory(self, product, location): + quant = self.env['stock.quant'].search([ + ('product_id', '=', product.id), + ('location_id', '=', location.id), + ]) + + if quant: + return quant.quantity + + return 0 + + qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id) + if line.qty_done > qty_onhand: + raise UserError('Quantity Done melebihi Quantity Onhand') def send_mail_bills(self): if self.picking_type_code == 'incoming' and self.purchase_id: @@ -1008,48 +1027,167 @@ class StockPicking(models.Model): return f'{formatted_fastest_eta} - {formatted_longest_eta}' -# class CheckProduct(models.Model): -# _name = 'check.product' -# _description = 'Check Product' -# _order = 'picking_id, id' - -# picking_id = fields.Many2one('stock.picking', string='Picking Reference', required=True, ondelete='cascade', index=True, copy=False) -# product_id = fields.Many2one('product.product', string='Product') - - -# @api.constrains('product_id') -# def check_product_validity(self): -# """ -# Validate if the product exists in the related stock.picking's move_ids_without_package -# and ensure that the product's quantity does not exceed the available product_uom_qty. -# """ -# for record in self: -# if not record.picking_id or not record.product_id: -# continue - -# # Filter move lines in the related picking for the selected product -# moves = record.picking_id.move_ids_without_package.filtered( -# lambda move: move.product_id.id == record.product_id.id -# ) - -# if not moves: -# raise UserError(( -# "The product '%s' is not available in the related stock picking's moves. " -# "Please check and try again." -# ) % record.product_id.display_name) - -# # Calculate the total entries for the product in check.product for the same picking -# product_entries_count = self.search_count([ -# ('picking_id', '=', record.picking_id.id), -# ('product_id', '=', record.product_id.id) -# ]) - -# # Sum the product_uom_qty for all relevant moves -# total_qty_in_moves = sum(moves.mapped('product_uom_qty')) - -# # Compare the count of entries against the available quantity -# if product_entries_count > total_qty_in_moves: -# raise UserError(( -# "The product '%s' exceeds the allowable quantity (%s) in the related stock picking's moves. " -# "You can only add it %s times." -# ) % (record.product_id.display_name, total_qty_in_moves, total_qty_in_moves)) +class CheckProduct(models.Model): + _name = 'check.product' + _description = 'Check Product' + _order = 'picking_id, id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + product_id = fields.Many2one('product.product', string='Product', required=True) + quantity = fields.Float(string='Quantity', default=1.0, required=True) + status = fields.Char(string='Status', compute='_compute_status') + + @api.depends('quantity') + def _compute_status(self): + for record in self: + moves = record.picking_id.move_ids_without_package.filtered( + lambda move: move.product_id.id == record.product_id.id + ) + total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + + if record.quantity < total_qty_in_moves: + record.status = 'Pending' + else: + record.status = 'Done' + + + def create(self, vals): + # Create the record + record = super(CheckProduct, self).create(vals) + # Ensure uniqueness after creation + if not self.env.context.get('skip_consolidate'): + record.with_context(skip_consolidate=True)._consolidate_duplicate_lines() + return record + + def write(self, vals): + # Write changes to the record + result = super(CheckProduct, self).write(vals) + # Ensure uniqueness after writing + if not self.env.context.get('skip_consolidate'): + self.with_context(skip_consolidate=True)._consolidate_duplicate_lines() + return result + + def _sync_check_product_to_moves(self, picking): + """ + Sinkronisasi quantity_done di move_ids_without_package + dengan total quantity dari check.product berdasarkan product_id. + """ + for product_id in picking.check_product_lines.mapped('product_id'): + # Totalkan quantity dari semua baris check.product untuk product_id ini + total_quantity = sum( + line.quantity for line in picking.check_product_lines.filtered(lambda line: line.product_id == product_id) + ) + # Update quantity_done di move yang relevan + moves = picking.move_ids_without_package.filtered(lambda move: move.product_id == product_id) + for move in moves: + move.quantity_done = total_quantity + + def _consolidate_duplicate_lines(self): + """ + Consolidate duplicate lines with the same product_id under the same picking_id + and sync the total quantity to related moves. + """ + for picking in self.mapped('picking_id'): + lines_to_remove = self.env['check.product'] # Recordset untuk menyimpan baris yang akan dihapus + product_lines = picking.check_product_lines.filtered(lambda line: line.product_id) + + # Group lines by product_id + product_groups = {} + for line in product_lines: + product_groups.setdefault(line.product_id.id, []).append(line) + + for product_id, lines in product_groups.items(): + if len(lines) > 1: + # Consolidate duplicate lines + first_line = lines[0] + total_quantity = sum(line.quantity for line in lines) + + # Update the first line's quantity + first_line.with_context(skip_consolidate=True).write({'quantity': total_quantity}) + + # Add the remaining lines to the lines_to_remove recordset + lines_to_remove |= self.env['check.product'].browse([line.id for line in lines[1:]]) + + # Perform unlink after consolidation + if lines_to_remove: + lines_to_remove.unlink() + + # Sync total quantities to moves + self._sync_check_product_to_moves(picking) + + @api.onchange('product_id', 'quantity') + def check_product_validity(self): + for record in self: + if not record.picking_id or not record.product_id: + continue + + # Filter moves related to the selected product + moves = record.picking_id.move_ids_without_package.filtered( + lambda move: move.product_id.id == record.product_id.id + ) + + if not moves: + raise UserError(( + "The product '%s' is not available in the related stock picking's moves. " + "Please check and try again." + ) % record.product_id.display_name) + + total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + + # Find existing lines for the same product, excluding the current line + existing_lines = record.picking_id.check_product_lines.filtered( + lambda line: line.product_id == record.product_id and line.id != record.id + ) + + if existing_lines: + # Get the first existing line + first_line = existing_lines[0] + + # Calculate the total quantity after addition + total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity + + if total_quantity > total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' sudah melebihi quantity demand: (%s)." + ) % (record.product_id.display_name)) + + else: + # Check if the quantity exceeds the allowed total + if record.quantity > total_qty_in_moves: + raise UserError(( + "Quantity Product '%s' sudah melebihi quantity demand: (%s)." + ) % (record.product_id.display_name)) + + # Set the quantity to the entered value + record.quantity = record.quantity + +class BarcodeProduct(models.Model): + _name = 'barcode.product' + _description = 'Barcode Product' + _order = 'picking_id, id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + product_id = fields.Many2one('product.product', string='Product', required=True) + barcode = fields.Char(string='Barcode') + + @api.constrains('barcode') + def send_barcode_to_product(self): + for record in self: + 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 -- cgit v1.2.3 From 6424192cdcb34ff2ea57a19b5ee41bf4bf68870c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 16 Jan 2025 13:29:35 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 05a7ee4a..cf5b0502 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -894,10 +894,20 @@ class StockPicking(models.Model): @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) return super(StockPicking, self).create(vals) def write(self, vals): self._use_faktur(vals) + for picking in self: + if (vals.get('picking_type_code', picking.picking_type_code) == 'incoming' and + vals.get('location_dest_id', picking.location_dest_id.id) == 58): + if 'name' in vals or picking.name.startswith('BU/IN/'): + name_to_modify = vals.get('name', picking.name) + if name_to_modify.startswith('BU/IN/'): + vals['name'] = name_to_modify.replace('BU/IN/', 'BU/INPUT/', 1) return super(StockPicking, self).write(vals) def _use_faktur(self, vals): -- cgit v1.2.3 From 11a561355208a403d635b16d6c306cc9f19eb714 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 16 Jan 2025 15:51:33 +0700 Subject: change name --- indoteknik_custom/models/stock_picking.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cf5b0502..4c19cb3a 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -897,17 +897,36 @@ class StockPicking(models.Model): 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 return super(StockPicking, self).create(vals) def write(self, vals): self._use_faktur(vals) for picking in self: + # Periksa apakah kondisi terpenuhi saat data diubah if (vals.get('picking_type_code', picking.picking_type_code) == 'incoming' and vals.get('location_dest_id', picking.location_dest_id.id) == 58): if 'name' in vals or picking.name.startswith('BU/IN/'): name_to_modify = vals.get('name', picking.name) if name_to_modify.startswith('BU/IN/'): vals['name'] = name_to_modify.replace('BU/IN/', 'BU/INPUT/', 1) + + if (vals.get('picking_type_code', picking.picking_type_code) == 'internal' and + vals.get('location_id', picking.location_id.id) == 58): + name_to_modify = vals.get('name', picking.name) + if name_to_modify.startswith('BU/INT'): + new_name = name_to_modify.replace('BU/INT', 'BU/IN', 1) + # Periksa apakah nama sudah ada + if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', picking.company_id.id)]) > 0: + new_name = f"{new_name}-DUP" + vals['name'] = new_name return super(StockPicking, self).write(vals) def _use_faktur(self, vals): -- cgit v1.2.3 From 2a47fdafcb12440c68e346d35d465b0a0c800945 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 23 Jan 2025 10:03:15 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4c19cb3a..cc86c451 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1164,8 +1164,7 @@ class CheckProduct(models.Model): if not moves: raise UserError(( - "The product '%s' is not available in the related stock picking's moves. " - "Please check and try again." + "The product '%s' tidak ada di operations. " ) % record.product_id.display_name) total_qty_in_moves = sum(moves.mapped('product_uom_qty')) @@ -1184,14 +1183,13 @@ class CheckProduct(models.Model): if total_quantity > total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand: (%s)." + "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) - else: # Check if the quantity exceeds the allowed total if record.quantity > total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand: (%s)." + "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) # Set the quantity to the entered value -- cgit v1.2.3 From de4404c4b60b0859a4cbae836cc4c99d806bf697 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 31 Jan 2025 10:01:49 +0700 Subject: fix bug --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cc86c451..9af74cd3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -467,7 +467,7 @@ class StockPicking(models.Model): def check_state_reserve(self): pickings = self.search([ - ('state', 'not in', ['cancel', 'draft', 'done']), + ('state', 'not in', ['cancel', 'done']), ('picking_type_code', '=', 'outgoing') ]) -- cgit v1.2.3 From b4249a4dbed1f982ce2355ea7b8245dd1c44da8d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 31 Jan 2025 11:20:33 +0700 Subject: fix send mail bills --- indoteknik_custom/models/stock_picking.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 9af74cd3..ce198be3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -811,6 +811,7 @@ class StockPicking(models.Model): self.calculate_line_no() self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' + self.send_mail_bills() return res -- cgit v1.2.3 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(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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 ++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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 -- cgit v1.2.3 From 751645803b13cbc96d4a554a9c9d8a63cd991486 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 11 Feb 2025 13:45:07 +0700 Subject: fix bug state reserve --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ce198be3..b1b1bdb8 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -467,19 +467,19 @@ class StockPicking(models.Model): def check_state_reserve(self): pickings = self.search([ - ('state', 'not in', ['cancel', 'done']), + ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'outgoing') ]) for picking in pickings: - fullfillments = self.env['sales.order.fullfillment'].search([ - ('sales_order_id', '=', picking.sale_id.id) + fullfillments = self.env['sales.order.fulfillment.v2'].search([ + ('sale_order_id', '=', picking.sale_id.id) ]) picking.state_reserve = 'ready' picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() - if any(rec.reserved_from not in ['Inventory On Hand', 'Reserved from stock', 'Free Stock'] for rec in fullfillments): + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): picking.state_reserve = 'waiting' picking.date_reserved = '' -- cgit v1.2.3 From eeff963c94f4d933b89308f40b387fd67ef881c4 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 11 Feb 2025 16:18:57 +0700 Subject: fix bug backorder state reserve --- indoteknik_custom/models/stock_picking.py | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b1b1bdb8..f49c493c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -470,6 +470,38 @@ class StockPicking(models.Model): ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'outgoing') ]) + + count = self.search_count([ + ('state', 'not in', ['cancel', 'draft', 'done']), + ('picking_type_code', '=', 'outgoing') + ]) + + for picking in pickings: + fullfillments = self.env['sales.order.fulfillment.v2'].search([ + ('sale_order_id', '=', picking.sale_id.id) + ]) + + picking.state_reserve = 'ready' + picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() + + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): + picking.state_reserve = 'waiting' + picking.date_reserved = '' + + self.check_state_reserve_backorder() + + def check_state_reserve_backorder(self): + pickings = self.search([ + ('backorder_id', '!=', False), + ('picking_type_code', '=', 'outgoing'), + ('state', 'not in', ['cancel', 'draft', 'done']) + ]) + + count = self.search_count([ + ('backorder_id', '!=', False), + ('picking_type_code', '=', 'outgoing'), + ('state', 'not in', ['cancel', 'draft', 'done']) + ]) for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ -- cgit v1.2.3 From 1c42539aeb53207b07bde3030d2316b58c2cf81e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 12 Feb 2025 09:41:59 +0700 Subject: trying to fix bug state reserve --- indoteknik_custom/models/stock_picking.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f49c493c..49e66786 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -468,7 +468,8 @@ class StockPicking(models.Model): def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'outgoing') + ('picking_type_code', '=', 'outgoing'), + ('name', 'ilike', 'BU/OUT/'), ]) count = self.search_count([ @@ -493,6 +494,7 @@ class StockPicking(models.Model): def check_state_reserve_backorder(self): pickings = self.search([ ('backorder_id', '!=', False), + ('name', 'ilike', 'BU/OUT/'), ('picking_type_code', '=', 'outgoing'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) -- 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/stock_picking.py | 171 ++++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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!')) -- 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/stock_picking.py | 192 ++++++++++++------------------ 1 file changed, 77 insertions(+), 115 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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 -- cgit v1.2.3 From 8696e202ecf594890a9ad29bc2bd2729321459c5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Feb 2025 14:58:11 +0700 Subject: add copy to field state_reserve and date_reserved --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 49e66786..954a5d52 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -94,7 +94,7 @@ class StockPicking(models.Model): purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id', string="Purchase Representative") carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method') shipping_status = fields.Char(string='Shipping Status', compute="_compute_shipping_status") - date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya') + date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False) status_printed = fields.Selection([ ('not_printed', 'Belum Print'), ('printed', 'Printed') @@ -120,7 +120,7 @@ class StockPicking(models.Model): ('ready', 'Ready to Ship'), ('done', 'Done'), ('cancel', 'Cancelled'), - ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") + ], string='Status Reserve', readonly=True, tracking=True, copy=False, help="The current state of the stock picking.") notee = fields.Text(string="Note") @api.model -- cgit v1.2.3 From b20368190fbf08b87d8a665c4b316786e3d73141 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 24 Feb 2025 15:36:22 +0700 Subject: state reserve --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 954a5d52..36d9f63d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -120,7 +120,7 @@ class StockPicking(models.Model): ('ready', 'Ready to Ship'), ('done', 'Done'), ('cancel', 'Cancelled'), - ], string='Status Reserve', readonly=True, tracking=True, copy=False, help="The current state of the stock picking.") + ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") notee = fields.Text(string="Note") @api.model -- 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_picking.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models/stock_picking.py') 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 --- indoteknik_custom/models/stock_picking.py | 104 +++++++++++++++++++----------- 1 file changed, 65 insertions(+), 39 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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 --- indoteknik_custom/models/stock_picking.py | 195 +++++++++++++++++++++--------- 1 file changed, 140 insertions(+), 55 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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'} + + -- 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(+) (limited to 'indoteknik_custom/models/stock_picking.py') 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 59f5be7f2145530979dcb0d0ff23197a4aa0c589 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 5 Mar 2025 14:31:32 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 212 +++++++++++++++++++++++++++--- 1 file changed, 194 insertions(+), 18 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 36d9f63d..17ef1228 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1,7 +1,7 @@ from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools.float_utils import float_is_zero -from datetime import timedelta, datetime +from datetime import timedelta, datetime as waktu from itertools import groupby import pytz, requests, json, requests from dateutil import parser @@ -12,10 +12,19 @@ import base64 import requests import time import logging +import re +from deep_translator import GoogleTranslator _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' + _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) is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') @@ -166,6 +175,68 @@ 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") + # Biteship Section + biteship_id = fields.Char(string="Biteship Respon ID") + biteship_tracking_id = fields.Char(string="Biteship Trackcking ID") + biteship_waybill_id = fields.Char(string="Biteship Waybill ID") + estimated_ready_ship_date = fields.Datetime(string='ET Ready to Ship', copy=False, related='sale_id.estimated_ready_ship_date') + countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) + countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) + final_seq = fields.Float(string='Sequance Order', index=True) + + + def schduled_update_sequance(self): + 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: + try : + if record.estimated_ready_ship_date and record.state not in ('cancel', 'done'): + rts = record.estimated_ready_ship_date - waktu.now() + rts_days = rts.days + rts_hours = divmod(rts.seconds, 3600) + + estimated_by_erts = rts.total_seconds() / 3600 + + record.countdown_ready_to_ship = f"{rts_days} days, {rts_hours} hours" + record.countdown_hours = estimated_by_erts + else: + record.countdown_hours = 999999999999 + record.countdown_ready_to_ship = False + except Exception as e : + _logger.error(f"Error calculating sequance {self.id}: {str(e)}") + print(str(e)) + return { 'error': str(e) } + + + # @api.depends('estimated_ready_ship_date', 'state') + # def _compute_countdown_hours(self): + # for record in self: + # if record.state in ('cancel', 'done') or not record.estimated_ready_ship_date: + # # Gunakan nilai yang sangat besar sebagai placeholder + # record.countdown_hours = 999999 + # else: + # delta = record.estimated_ready_ship_date - waktu.now() + # record.countdown_hours = delta.total_seconds() / 3600 + + # @api.depends('estimated_ready_ship_date', 'state') + # def _compute_countdown_ready_to_ship(self): + # for record in self: + # if record.state in ('cancel', 'done'): + # record.countdown_ready_to_ship = False + # else: + # if record.estimated_ready_ship_date: + # delta = record.estimated_ready_ship_date - waktu.now() + # days = delta.days + # hours, remainder = divmod(delta.seconds, 3600) + # record.countdown_ready_to_ship = f"{days} days, {hours} hours" + # record.countdown_hours = delta.total_seconds() / 3600 + # else: + # record.countdown_ready_to_ship = False + def _compute_lalamove_image_html(self): for record in self: if record.lalamove_image_url: @@ -321,7 +392,7 @@ class StockPicking(models.Model): picking.tracking_by = self.env.user.id ata_at_str = data.get("ata_at") envio_ata = self._convert_to_datetime(data.get("ata_at")) - + picking.driver_arrival_date = envio_ata if data.get("status") != 'delivered': picking.driver_arrival_date = False @@ -332,12 +403,13 @@ class StockPicking(models.Model): raise UserError(f"Kesalahan tidak terduga: {str(e)}") def action_send_to_biteship(self): - url = "https://api.biteship.com/v1/orders" - api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" - + + if self.biteship_tracking_id: + raise UserError(f"Order ini sudah dikirim ke Biteship. Dengan Tracking Id: {self.biteship_tracking_id}") + # Mencari data sale.order.line berdasarkan sale_id products = self.env['sale.order.line'].search([('order_id', '=', self.sale_id.id)]) - + # Fungsi untuk membangun items_data dari order lines def build_items_data(lines): return [{ @@ -370,6 +442,7 @@ class StockPicking(models.Model): }) payload = { + "reference_id " : self.sale_id.name, "shipper_contact_name": self.carrier_id.pic_name or '', "shipper_contact_phone": self.carrier_id.pic_phone or '', "shipper_organization": self.carrier_id.name, @@ -381,7 +454,8 @@ class StockPicking(models.Model): "destination_contact_phone": self.real_shipping_id.phone or self.real_shipping_id.mobile, "destination_address": self.real_shipping_id.street, "destination_postal_code": self.real_shipping_id.zip, - "courier_type": "reg", + "origin_note": "BELAKANG INDOMARET", + "courier_type": self.sale_id.delivery_service_type or "reg", "courier_company": self.carrier_id.name.lower(), "delivery_type": "now", "destination_postal_code": self.real_shipping_id.zip, @@ -389,27 +463,42 @@ class StockPicking(models.Model): } # Cek jika pengiriman instant atau same_day - if "instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type: + if self.sale_id.delivery_service_type and ("instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): payload.update({ - "origin_note": "BELAKANG INDOMARET", - "courier_company": self.carrier_id.name.lower(), - "courier_type": self.sale_id.delivery_service_type, - "delivery_type": "now", - "items": items_data_instant # Gunakan items untuk instant + "origin_coordinate" :{ + "latitude": -6.3031123, + "longitude" : 106.7794934999 + }, + "destination_coordinate" : { + "latitude": self.real_shipping_id.latitude, + "longitude": self.real_shipping_id.longtitude, + }, + "items": items_data_instant }) - + + api_key = _biteship_api_key headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } # Kirim request ke Biteship - response = requests.post(url, headers=headers, json=payload) + response = requests.post(_biteship_url+'/orders', headers=headers, json=payload) + + if response.status_code == 200: + data = response.json() + + self.biteship_id = data.get("id", "") + self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "") + self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "") + self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "") - if response.status_code == 201: - return response.json() + return data else: - raise UserError(f"Error saat mengirim ke Biteship: {response.content}") + error_data = response.json() + error_message = error_data.get("error", "Unknown error") + error_code = error_data.get("code", "No code provided") + raise UserError(f"Error saat mengirim ke Biteship: {error_message} (Code: {error_code})") @api.constrains('driver_departure_date') def constrains_driver_departure_date(self): @@ -1039,11 +1128,14 @@ class StockPicking(models.Model): def get_tracking_detail(self): self.ensure_one() + + order = self.env['sale.order'].search([('name', '=', self.sale_id.name)], limit=1) response = { 'delivery_order': { 'name': self.name, 'carrier': self.carrier_id.name or '', + 'service' : order.delivery_service_type or '', 'receiver_name': '', 'receiver_city': '' }, @@ -1052,8 +1144,21 @@ class StockPicking(models.Model): 'waybill_number': self.delivery_tracking_no or '', 'delivery_status': None, 'eta': self.generate_eta_delivery(), + 'is_biteship': True if self.biteship_id else False, 'manifests': self.get_manifests() } + + if self.biteship_id : + histori = self.get_manifest_biteship() + eta_start = order.date_order + timedelta(days=order.estimated_arrival_days_start) + eta_end = order.date_order + timedelta(days=order.estimated_arrival_days) + formatted_eta = f"{eta_start.strftime('%d %b')} - {eta_end.strftime('%d %b %Y')}" + response['eta'] = formatted_eta + response['manifests'] = histori.get("manifests", []) + response['delivered'] = histori.get("delivered", False) or self.sj_return_date != False or self.driver_arrival_date != False + response['status'] = self._map_status_biteship(histori.get("delivered")) + + return response if not self.waybill_id or len(self.waybill_id.manifest_ids) == 0: response['delivered'] = self.sj_return_date != False or self.driver_arrival_date != False @@ -1066,6 +1171,77 @@ class StockPicking(models.Model): return response + def get_manifest_biteship(self): + api_key = _biteship_api_key + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" + } + + + manifests = [] + + try: + # Kirim request ke Biteship + response = requests.get(_biteship_url+'/trackings/'+self.biteship_tracking_id, headers=headers, json=manifests) + result = response.json() + description = { + 'confirmed' : 'Indoteknik telah melakukan permintaan pick-up', + 'allocated' : 'Kurir akan melakukan pick-up pesanan', + 'picking_up' : 'Kurir sedang dalam perjalanan menuju lokasi pick-up', + 'picked' : 'Pesanan sudah di pick-up kurir '+result.get("courier", {}).get("name", ""), + 'on_hold' : 'Pesanan ditahan sementara karena masalah pengiriman', + 'dropping_off' : 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', + 'delivered' : 'Pesanan telah sampai dan diterima oleh '+result.get("destination", {}).get("contact_name", "") + } + if(result.get('success') == True): + history = result.get("history", []) + status = result.get("status", "") + + for entry in reversed(history): + manifests.append({ + "status": re.sub(r'[^a-zA-Z0-9\s]', ' ', entry["status"]).lower().capitalize(), + "datetime": self._convert_to_local_time(entry["updated_at"]), + # "description": GoogleTranslator(source='auto', target='id').translate(entry["note"]), + "description": description[entry["status"]], + }) + + return { + "manifests": manifests, + "delivered": status + } + + return manifests + except Exception as e : + _logger.error(f"Error fetching Biteship order for picking {self.id}: {str(e)}") + return { 'error': str(e) } + + def _convert_to_local_time(self, iso_date): + try: + dt_with_tz = waktu.fromisoformat(iso_date) + utc_dt = dt_with_tz.astimezone(pytz.utc) + + local_tz = pytz.timezone("Asia/Jakarta") + local_dt = utc_dt.astimezone(local_tz) + + return local_dt.strftime("%Y-%m-%d %H:%M:%S") + except Exception as e: + return str(e) + + def _map_status_biteship(self, status): + status_mapping = { + "confirmed": "pending", + "scheduled": "pending", + "allocated": "pending", + "picking_up": "pending", + "picked": "shipment", + "cancelled": "cancelled", + "on_hold": "on_hold", + "dropping_off": "shipment", + "delivered": "completed" + } + return status_mapping.get(status, "Hubungi Admin") + def generate_eta_delivery(self): current_date = datetime.datetime.now() prepare_days = 3 -- cgit v1.2.3 From 0554da8389d1622dc91aafee74390de888db9a6f Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 5 Mar 2025 22:03:13 +0700 Subject: update stock picing --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 17ef1228..89531b79 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -182,7 +182,7 @@ class StockPicking(models.Model): estimated_ready_ship_date = fields.Datetime(string='ET Ready to Ship', copy=False, related='sale_id.estimated_ready_ship_date') countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) - final_seq = fields.Float(string='Sequance Order', index=True) + final_seq = fields.Float(string='Remaining Time') def schduled_update_sequance(self): @@ -207,7 +207,7 @@ class StockPicking(models.Model): record.countdown_hours = 999999999999 record.countdown_ready_to_ship = False except Exception as e : - _logger.error(f"Error calculating sequance {self.id}: {str(e)}") + _logger.error(f"Error calculating sequance {record.id}: {str(e)}") print(str(e)) return { 'error': str(e) } -- cgit v1.2.3 From 4115129907c5525c4688f2e6a3c28e0f249025b2 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 5 Mar 2025 22:07:00 +0700 Subject: bug fix --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 89531b79..6e003d24 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -180,8 +180,8 @@ class StockPicking(models.Model): biteship_tracking_id = fields.Char(string="Biteship Trackcking ID") biteship_waybill_id = fields.Char(string="Biteship Waybill ID") estimated_ready_ship_date = fields.Datetime(string='ET Ready to Ship', copy=False, related='sale_id.estimated_ready_ship_date') - countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) - countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False) + # countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) + # 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') -- cgit v1.2.3 From 1abd80916c1ec843343dacf2cd3a359651e7cd43 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 6 Mar 2025 09:20:32 +0700 Subject: comment relate field --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6e003d24..217f234e 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -179,7 +179,7 @@ class StockPicking(models.Model): biteship_id = fields.Char(string="Biteship Respon ID") biteship_tracking_id = fields.Char(string="Biteship Trackcking ID") biteship_waybill_id = fields.Char(string="Biteship Waybill ID") - estimated_ready_ship_date = fields.Datetime(string='ET Ready to Ship', copy=False, related='sale_id.estimated_ready_ship_date') + # estimated_ready_ship_date = fields.Datetime(string='ET Ready to Ship', copy=False, related='sale_id.estimated_ready_ship_date') # countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) # 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') -- 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 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') 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: -- cgit v1.2.3 From c80a48e26b972b203229e128209bb8da89d5da57 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Mon, 10 Mar 2025 04:36:11 +0700 Subject: bugs fix rts date --- indoteknik_custom/models/stock_picking.py | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 217f234e..b8bdcd94 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -190,26 +190,26 @@ class StockPicking(models.Model): self.env.cr.execute(query) - @api.depends('estimated_ready_ship_date', 'state') - def _callculate_sequance(self): - for record in self: - try : - if record.estimated_ready_ship_date and record.state not in ('cancel', 'done'): - rts = record.estimated_ready_ship_date - waktu.now() - rts_days = rts.days - rts_hours = divmod(rts.seconds, 3600) + # @api.depends('estimated_ready_ship_date', 'state') + # def _callculate_sequance(self): + # for record in self: + # try : + # if record.estimated_ready_ship_date and record.state not in ('cancel', 'done'): + # rts = record.estimated_ready_ship_date - waktu.now() + # rts_days = rts.days + # rts_hours = divmod(rts.seconds, 3600) - estimated_by_erts = rts.total_seconds() / 3600 + # estimated_by_erts = rts.total_seconds() / 3600 - record.countdown_ready_to_ship = f"{rts_days} days, {rts_hours} hours" - record.countdown_hours = estimated_by_erts - else: - record.countdown_hours = 999999999999 - record.countdown_ready_to_ship = False - except Exception as e : - _logger.error(f"Error calculating sequance {record.id}: {str(e)}") - print(str(e)) - return { 'error': str(e) } + # record.countdown_ready_to_ship = f"{rts_days} days, {rts_hours} hours" + # record.countdown_hours = estimated_by_erts + # else: + # record.countdown_hours = 999999999999 + # record.countdown_ready_to_ship = False + # except Exception as e : + # _logger.error(f"Error calculating sequance {record.id}: {str(e)}") + # print(str(e)) + # return { 'error': str(e) } # @api.depends('estimated_ready_ship_date', 'state') -- cgit v1.2.3 From 447edfb87520d5d1475eeba43f1cd3425222cd4d Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Mon, 10 Mar 2025 10:19:18 +0700 Subject: udate final_seq --- indoteknik_custom/models/stock_picking.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b8bdcd94..ab8109c7 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -934,6 +934,7 @@ class StockPicking(models.Model): self.calculate_line_no() self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' + self.final_seq = 0 self.send_mail_bills() return res -- 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 --- 1 file changed, 3 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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: -- cgit v1.2.3 From 2b1783368e7be632e18be524b5288713125e7902 Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Thu, 13 Mar 2025 11:25:22 +0700 Subject: uat bitehsip --- indoteknik_custom/models/stock_picking.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ab8109c7..edc9cc78 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -13,11 +13,11 @@ import requests import time import logging import re -from deep_translator import GoogleTranslator _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" -_biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" +_biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" +# _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" @@ -492,8 +492,18 @@ class StockPicking(models.Model): self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "") self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "") self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "") + + waybill_id = data.get("courier", {}).get("waybill_id", "") + + message = f"✅ Berhasil Order ke Biteship! Resi: {waybill_id}" if waybill_id else "⚠️ Order berhasil, tetapi tidak ada nomor resi." - return data + return { + 'effect': { + 'fadeout': 'slow', # Efek menghilang perlahan + 'message': message, # Pesan sukses + 'type': 'rainbow_man', # Efek animasi lucu Odoo + } + } else: error_data = response.json() error_message = error_data.get("error", "Unknown error") @@ -1203,7 +1213,6 @@ class StockPicking(models.Model): manifests.append({ "status": re.sub(r'[^a-zA-Z0-9\s]', ' ', entry["status"]).lower().capitalize(), "datetime": self._convert_to_local_time(entry["updated_at"]), - # "description": GoogleTranslator(source='auto', target='id').translate(entry["note"]), "description": description[entry["status"]], }) -- cgit v1.2.3 From 0f962b64b4c62e236d6096c594d7fbe8c49276d8 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 11:50:08 +0700 Subject: md selish --- indoteknik_custom/models/stock_picking.py | 40 ++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ab8109c7..1e93da80 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -131,6 +131,16 @@ class StockPicking(models.Model): ('cancel', 'Cancelled'), ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") notee = fields.Text(string="Note") + state_approve_md = fields.Selection([ + ('waiting', 'Waiting For Approve by MD'), + ('pending', 'Pending (cari dulu barangnya)'), + ('done', 'Approve by MD'), + ], string='Approval MD Gudang Selisih', tracking=True, copy=False, help="The current state of the MD Approval transfer barang from gudang selisih.") + show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") + + def _compute_show_state_approve_md(self): + for record in self: + record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" @api.model def _compute_dokumen_tanda_terima(self): @@ -881,8 +891,36 @@ class StockPicking(models.Model): qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id) if line.qty_done > qty_onhand: raise UserError('Quantity Done melebihi Quantity Onhand') + def button_state_approve_md(self): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + active_model = self.env.context.get('active_model') + if self.env.user.id in users_in_group.mapped('id'): + self.state_approve_md = 'done' + else: + raise UserError('Hanya MD yang bisa Approve') + + def button_state_pending_md(self): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + active_model = self.env.context.get('active_model') + if self.env.user.id in users_in_group.mapped('id'): + self.state_approve_md = 'pending' + else: + raise UserError('Hanya MD yang bisa Approve') def button_validate(self): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + active_model = self.env.context.get('active_model') + if self.location_id.id == 47 and self.env.user.id not in users_in_group.mapped('id') and self.state_approve_md != 'done': + self.state_approve_md = 'waiting' if self.state_approve_md != 'pending' else 'pending' + self.env.cr.commit() + raise UserError("Transfer dari gudang selisih harus di approve MD, Hubungi MD agar bisa di Validate") + else: + if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): + self.state_approve_md = 'done' + 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") @@ -907,7 +945,7 @@ class StockPicking(models.Model): if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: raise UserError("Harus di Approve oleh Logistik") - + if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager: raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara") -- cgit v1.2.3 From e7a1a6a1fbbc7e74291471d2abc9487511a8a861 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 18 Mar 2025 15:39:13 +0700 Subject: fix code md gudang selisih --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1e93da80..9ea9f2a2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -133,7 +133,7 @@ class StockPicking(models.Model): notee = fields.Text(string="Note") state_approve_md = fields.Selection([ ('waiting', 'Waiting For Approve by MD'), - ('pending', 'Pending (cari dulu barangnya)'), + ('pending', 'Pending (perlu koordinasi dengan MD)'), ('done', 'Approve by MD'), ], string='Approval MD Gudang Selisih', tracking=True, copy=False, help="The current state of the MD Approval transfer barang from gudang selisih.") show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") -- cgit v1.2.3 From 4a7b5ebc82de37c6d2bde5e670066336256939d5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Mar 2025 09:55:02 +0700 Subject: cr reklas uang muka and permission button cancel stock picking --- indoteknik_custom/models/stock_picking.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ab8109c7..4229d33e 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1008,9 +1008,12 @@ class StockPicking(models.Model): return True def action_cancel(self): - if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': + if not self.env.user.is_logistic_approver: if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") + + if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group('indoteknik_custom.group_role_logistic'): + raise UserError("Button ini hanya untuk Logistik") res = super(StockPicking, self).action_cancel() return res -- cgit v1.2.3 From 1f324148f176bafc471a5948b8c5322a9b175ffa Mon Sep 17 00:00:00 2001 From: trisusilo48 Date: Wed, 19 Mar 2025 12:49:44 +0700 Subject: request iman --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 9e5fca66..23ddb47f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -136,11 +136,11 @@ class StockPicking(models.Model): ('pending', 'Pending (perlu koordinasi dengan MD)'), ('done', 'Approve by MD'), ], string='Approval MD Gudang Selisih', tracking=True, copy=False, help="The current state of the MD Approval transfer barang from gudang selisih.") - show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") + # show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") - def _compute_show_state_approve_md(self): - for record in self: - record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" + # def _compute_show_state_approve_md(self): + # for record in self: + # record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" @api.model def _compute_dokumen_tanda_terima(self): -- cgit v1.2.3 From 99cf4f8d2a109f09049a52bccdb85dde6aec1081 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Mar 2025 13:17:52 +0700 Subject: fix bug --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 23ddb47f..b8a83d5c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1056,7 +1056,7 @@ class StockPicking(models.Model): return True def action_cancel(self): - if not self.env.user.is_logistic_approver: + if not self.env.user.is_logistic_approver and (self.env.context.get('active_model') == 'stock.picking' or self.env.context.get('active_model') == 'stock.picking.type'): if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") -- cgit v1.2.3 From cc1759574f76b084a1ce44e1acf01ed20dcdd729 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 19 Mar 2025 13:34:25 +0700 Subject: fix bug button cancel stock picking --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b8a83d5c..6c6cbaa1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1060,7 +1060,7 @@ class StockPicking(models.Model): if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") - if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group('indoteknik_custom.group_role_logistic'): + if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group('indoteknik_custom.group_role_logistic') and self.picking_type_code == 'outgoing': raise UserError("Button ini hanya untuk Logistik") res = super(StockPicking, self).action_cancel() -- 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 ++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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' -- 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(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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 20cf871492e9de61669c577d31d2219d092796bf Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 26 Mar 2025 15:02:37 +0700 Subject: fix bug on stock picking --- indoteknik_custom/models/stock_picking.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6c6cbaa1..a1b28385 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -96,6 +96,7 @@ class StockPicking(models.Model): ('not_paid', 'Customer belum bayar'), ('partial', 'Kirim Parsial'), ('indent', 'Indent'), + ('waiting_schedule', 'Menunggu Jadwal Kirim'), ('self_pickup', 'Barang belum di pickup Customer'), ('expedition_closed', 'Eskpedisi belum buka') ], string='Note Logistic', help='jika field ini diisi maka tidak akan dihitung ke lead time') -- 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(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') 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): -- cgit v1.2.3 From 4147989e776d82a0a8b06a0ff8901e2146b0bd57 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 13:52:14 +0700 Subject: comment validasi validate bu out --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b1243e95..18edd497 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -995,11 +995,11 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - 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.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 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")) -- cgit v1.2.3 From a24177e4f4f575ea95ebc1d886b830da5c320690 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 14:01:52 +0700 Subject: fix case old so and new so wms validation --- indoteknik_custom/models/stock_picking.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 18edd497..1987c03c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -995,11 +995,19 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - # 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.")) + threshold_datetime = datetime(2025, 4, 11, 13, 26) + + if (len(self.konfirm_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and (self.create_date or datetime.now()) > threshold_datetime): + 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' + and (self.create_date or datetime.now()) > threshold_datetime): + 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")) -- cgit v1.2.3 From 52b493aaee7c1782c328d2f3af7bee6534342734 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 14:06:29 +0700 Subject: fix error --- indoteknik_custom/models/stock_picking.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1987c03c..932e394b 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -995,18 +995,18 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - threshold_datetime = datetime(2025, 4, 11, 13, 26) + threshold_datetime = waktu(2025, 4, 11, 13, 26) if (len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing' - and (self.create_date or datetime.now()) > threshold_datetime): + and (self.create_date or waktu.utcnow()) > threshold_datetime): 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' - and (self.create_date or datetime.now()) > threshold_datetime): + and (self.create_date or waktu.utcnow()) > threshold_datetime): 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': -- cgit v1.2.3 From 9ca20d51a0aad50ea3df9bd878735c2fb8aadcc3 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 15:32:39 +0700 Subject: push wms --- indoteknik_custom/models/stock_picking.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 932e394b..19b7517c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -148,8 +148,16 @@ class StockPicking(models.Model): # for record in self: # record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" quantity_koli = fields.Float(string="Quantity Koli", copy=False) + total_mapping_koli = fields.Float(string="Total Mapping Koli", compute='_compute_total_mapping_koli') - + @api.depends('konfirm_koli_lines', 'konfirm_koli_lines.pick_id', 'konfirm_koli_lines.pick_id.quantity_koli') + def _compute_total_mapping_koli(self): + for record in self: + total = 0.0 + for line in record.konfirm_koli_lines: + if line.pick_id and line.pick_id.quantity_koli: + total += line.pick_id.quantity_koli + record.total_mapping_koli = total @api.model def _compute_dokumen_tanda_terima(self): @@ -995,18 +1003,18 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - threshold_datetime = waktu(2025, 4, 11, 13, 26) + threshold_datetime = waktu(2025, 4, 11, 6, 26) if (len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing' - and (self.create_date or waktu.utcnow()) > threshold_datetime): + and self.create_date > threshold_datetime): 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' - and (self.create_date or waktu.utcnow()) > threshold_datetime): + and self.create_date > threshold_datetime): 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': @@ -1015,9 +1023,12 @@ class StockPicking(models.Model): if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada koli! Harap periksa kembali.")) + if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name: + raise UserError(_("Tidak ada Check Product! 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)) + % (self.total_koli, self.t1otal_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: @@ -1847,7 +1858,7 @@ class KonfirmKoli(models.Model): 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' -- cgit v1.2.3 From 1def3707b2392fa17fb71cc70051bbe76cda47aa Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 11 Apr 2025 17:03:27 +0700 Subject: change request state reserve stock picking --- indoteknik_custom/models/stock_picking.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 19b7517c..fd9daec9 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -649,13 +649,13 @@ class StockPicking(models.Model): def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'outgoing'), - ('name', 'ilike', 'BU/OUT/'), + ('picking_type_code', '=', 'internal'), + ('name', 'ilike', 'BU/PICK/'), ]) count = self.search_count([ ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'outgoing') + ('picking_type_code', '=', 'internal') ]) for picking in pickings: @@ -675,8 +675,8 @@ class StockPicking(models.Model): def check_state_reserve_backorder(self): pickings = self.search([ ('backorder_id', '!=', False), - ('name', 'ilike', 'BU/OUT/'), - ('picking_type_code', '=', 'outgoing'), + ('name', 'ilike', 'BU/PICK/'), + ('picking_type_code', '=', 'internal'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) -- cgit v1.2.3 From 487772c771e72a77ae4d9e9c865d94d015f1ea5b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 10:44:12 +0700 Subject: refactor code state_reserve --- indoteknik_custom/models/stock_picking.py | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index fd9daec9..558e13e6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -215,6 +215,7 @@ class StockPicking(models.Model): # countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) # 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') + shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): @@ -626,37 +627,15 @@ class StockPicking(models.Model): res = super(StockPicking, self).do_unreserve() current_time = datetime.datetime.utcnow() self.date_unreserve = current_time - # self.check_state_reserve() return res - # def check_state_reserve(self): - # do = self.search([ - # ('state', 'not in', ['cancel', 'draft', 'done']), - # ('picking_type_code', '=', 'outgoing') - # ]) - - # for rec in do: - # rec.state_reserve = 'ready' - # rec.date_reserved = datetime.datetime.utcnow() - - # for line in rec.move_ids_without_package: - # if line.product_uom_qty > line.reserved_availability: - # rec.state_reserve = 'waiting' - # rec.date_reserved = '' - # break - def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'internal'), ('name', 'ilike', 'BU/PICK/'), ]) - - count = self.search_count([ - ('state', 'not in', ['cancel', 'draft', 'done']), - ('picking_type_code', '=', 'internal') - ]) for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ @@ -679,12 +658,6 @@ class StockPicking(models.Model): ('picking_type_code', '=', 'internal'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) - - count = self.search_count([ - ('backorder_id', '!=', False), - ('picking_type_code', '=', 'outgoing'), - ('state', 'not in', ['cancel', 'draft', 'done']) - ]) for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ -- cgit v1.2.3 From 17b8688b83b65b0c21034ffcc9e51baf1099618b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 11:19:41 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 558e13e6..2aca5003 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -978,17 +978,17 @@ class StockPicking(models.Model): threshold_datetime = waktu(2025, 4, 11, 6, 26) - if (len(self.konfirm_koli_lines) == 0 - and 'BU/OUT/' in self.name - and self.picking_type_code == 'outgoing' - and self.create_date > threshold_datetime): - 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' - and self.create_date > threshold_datetime): - raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + # if (len(self.konfirm_koli_lines) == 0 + # and 'BU/OUT/' in self.name + # and self.picking_type_code == 'outgoing' + # and self.create_date > threshold_datetime): + # 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' + # and self.create_date > threshold_datetime): + # 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")) -- cgit v1.2.3 From 14fb9b00d18a7a0a3746106a1303f4ff1c13c356 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 11:21:06 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 2aca5003..558e13e6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -978,17 +978,17 @@ class StockPicking(models.Model): threshold_datetime = waktu(2025, 4, 11, 6, 26) - # if (len(self.konfirm_koli_lines) == 0 - # and 'BU/OUT/' in self.name - # and self.picking_type_code == 'outgoing' - # and self.create_date > threshold_datetime): - # 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' - # and self.create_date > threshold_datetime): - # raise UserError(_("Tidak ada scan koli! Harap periksa kembali.")) + if (len(self.konfirm_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and self.create_date > threshold_datetime): + 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' + and self.create_date > threshold_datetime): + 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")) -- cgit v1.2.3 From b73d16bf8dc0546190c9853f3e32a9aeaae3c1f0 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 13:17:02 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 558e13e6..4e926e60 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1056,6 +1056,7 @@ class StockPicking(models.Model): res = super(StockPicking, self).button_validate() self.calculate_line_no() self.date_done = datetime.datetime.utcnow() + self.driver_departure_date = datetime.datetime.utcnow() self.state_reserve = 'done' self.final_seq = 0 self.send_koli_to_so() -- cgit v1.2.3 From a4d19c6b9f026cc247c135b14a6fecf76a9fcd70 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 15:17:17 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 55 ++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4e926e60..76ba51d4 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -76,6 +76,11 @@ class StockPicking(models.Model): readonly=True, copy=False ) + out_code = fields.Integer( + string="Out Code", + readonly=True, + related="id", + ) sj_documentation = fields.Binary(string="Dokumentasi Surat Jalan", ) paket_documentation = fields.Binary(string="Dokumentasi Paket", ) sj_return_date = fields.Datetime(string="SJ Return Date", ) @@ -216,6 +221,16 @@ 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') shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') + state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') + + @api.constrains('konfirm_koli_lines') + def _constrains_konfirm_koli_lines(self): + now = datetime.datetime.utcnow() + for picking in self: + if len(picking.konfirm_koli_lines) > 0: + picking.state_packing = 'packing_done' + else: + picking.state_packing = 'not_packing' @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): @@ -1059,6 +1074,7 @@ class StockPicking(models.Model): self.driver_departure_date = datetime.datetime.utcnow() self.state_reserve = 'done' self.final_seq = 0 + self.set_picking_code_out() self.send_koli_to_so() if not self.env.context.get('skip_koli_check'): for picking in self: @@ -1088,6 +1104,26 @@ class StockPicking(models.Model): self.send_mail_bills() return res + def set_picking_code_out(self): + for picking in self: + # Check if picking meets criteria + is_bu_pick = picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name + if not is_bu_pick: + continue + + # Find matching outgoing transfers + bu_out_transfers = self.search([ + ('name', 'like', 'BU/OUT/%'), + ('sale_id', '=', picking.sale_id.id), + ('picking_type_code', '=', 'outgoing'), + ('picking_code', '=', False), + ('state', 'not in', ['done', 'cancel']) + ]) + + # Assign sequence code to each matching transfer + for transfer in bu_out_transfers: + transfer.picking_code = self.env['ir.sequence'].next_by_code('stock.picking.code') + def check_koli(self): for picking in self: @@ -1206,11 +1242,28 @@ class StockPicking(models.Model): def create(self, vals): self._use_faktur(vals) records = super(StockPicking, self).create(vals) - + + # Panggil sync_sale_line setelah record dibuat + # records.sync_sale_line(vals) return records + def sync_sale_line(self, vals): + # Pastikan kita bekerja dengan record yang sudah ada + for picking in self: + if picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name: + for line in picking.move_ids_without_package: + if line.product_id and picking.sale_id: + sale_line = self.env['sale.order.line'].search([ + ('product_id', '=', line.product_id.id), + ('order_id', '=', picking.sale_id.id) + ], limit=1) # Tambahkan limit=1 untuk efisiensi + + if sale_line: + line.sale_line_id = sale_line.id + def write(self, vals): self._use_faktur(vals) + self.sync_sale_line(vals) for picking in self: # Periksa apakah kondisi terpenuhi saat data diubah if (vals.get('picking_type_code', picking.picking_type_code) == 'incoming' and -- cgit v1.2.3 From 3e22bea62b4c57268ce777d34ec6d19aede8b0c1 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 14 Apr 2025 16:13:00 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 76ba51d4..8755a1f3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -154,6 +154,7 @@ class StockPicking(models.Model): # record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" quantity_koli = fields.Float(string="Quantity Koli", copy=False) total_mapping_koli = fields.Float(string="Total Mapping Koli", compute='_compute_total_mapping_koli') + so_lama = fields.Boolean('SO LAMA') @api.depends('konfirm_koli_lines', 'konfirm_koli_lines.pick_id', 'konfirm_koli_lines.pick_id.quantity_koli') def _compute_total_mapping_koli(self): @@ -996,13 +997,15 @@ class StockPicking(models.Model): if (len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing' - and self.create_date > threshold_datetime): + and self.create_date > threshold_datetime + and not self.so_lama): 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' - and self.create_date > threshold_datetime): + and self.create_date > threshold_datetime + and not self.so_lama): 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': -- cgit v1.2.3 From 3176f8497009169294e25f7f461f1a81c6bd0c59 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 15 Apr 2025 11:23:52 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 47 ++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 8755a1f3..ae17b5d1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -155,7 +155,32 @@ class StockPicking(models.Model): quantity_koli = fields.Float(string="Quantity Koli", copy=False) total_mapping_koli = fields.Float(string="Total Mapping Koli", compute='_compute_total_mapping_koli') so_lama = fields.Boolean('SO LAMA') - + linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out') + + # def write(self, vals): + # if 'linked_manual_bu_out' in vals: + # for record in self: + # if (record.picking_type_code == 'internal' + # and 'BU/PICK/' in record.name): + # # Jika menghapus referensi (nilai di-set False/None) + # if record.linked_manual_bu_out and not vals['linked_manual_bu_out']: + # record.linked_manual_bu_out.state_packing = 'not_packing' + # # Jika menambahkan referensi baru + # elif vals['linked_manual_bu_out']: + # new_picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out']) + # new_picking.state_packing = 'packing_done' + # return super().write(vals) + + # @api.model + # def create(self, vals): + # record = super().create(vals) + # if (record.picking_type_code == 'internal' + # and 'BU/PICK/' in record.name + # and vals.get('linked_manual_bu_out')): + # picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out']) + # picking.state_packing = 'packing_done' + # return record + @api.depends('konfirm_koli_lines', 'konfirm_koli_lines.pick_id', 'konfirm_koli_lines.pick_id.quantity_koli') def _compute_total_mapping_koli(self): for record in self: @@ -224,15 +249,6 @@ class StockPicking(models.Model): shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') - @api.constrains('konfirm_koli_lines') - def _constrains_konfirm_koli_lines(self): - now = datetime.datetime.utcnow() - for picking in self: - if len(picking.konfirm_koli_lines) > 0: - picking.state_packing = 'packing_done' - else: - picking.state_packing = 'not_packing' - @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): now = datetime.datetime.utcnow() @@ -1265,6 +1281,17 @@ class StockPicking(models.Model): line.sale_line_id = sale_line.id def write(self, vals): + if 'linked_manual_bu_out' in vals: + for record in self: + if (record.picking_type_code == 'internal' + and 'BU/PICK/' in record.name): + # Jika menghapus referensi (nilai di-set False/None) + if record.linked_manual_bu_out and not vals['linked_manual_bu_out']: + record.linked_manual_bu_out.state_packing = 'not_packing' + # Jika menambahkan referensi baru + elif vals['linked_manual_bu_out']: + new_picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out']) + new_picking.state_packing = 'packing_done' self._use_faktur(vals) self.sync_sale_line(vals) for picking in self: -- cgit v1.2.3 From d04df4f0f78e34dbaf1ce7f8ea1102ac2bdf019c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 15 Apr 2025 11:41:14 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ae17b5d1..383c75a3 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1030,6 +1030,9 @@ class StockPicking(models.Model): if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada koli! Harap periksa kembali.")) + if not self.linked_manual_bu_out and 'BU/PICK/' in self.name: + raise UserError(_("Isi BU Out terlebih dahulu!")) + if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada Check Product! Harap periksa kembali.")) @@ -1090,7 +1093,6 @@ class StockPicking(models.Model): res = super(StockPicking, self).button_validate() self.calculate_line_no() self.date_done = datetime.datetime.utcnow() - self.driver_departure_date = datetime.datetime.utcnow() self.state_reserve = 'done' self.final_seq = 0 self.set_picking_code_out() -- cgit v1.2.3 From e2a9f4f82b0c761cc4c20d501ab586239c0126a3 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 16 Apr 2025 11:51:03 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 383c75a3..3aa3a0a4 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1542,9 +1542,22 @@ class CheckProduct(models.Model): index=True, copy=False, ) - product_id = fields.Many2one('product.product', string='Product', required=True) - quantity = fields.Float(string='Quantity', default=1.0, required=True) + product_id = fields.Many2one('product.product', string='Product') + quantity = fields.Float(string='Quantity', default=1.0) status = fields.Char(string='Status', compute='_compute_status') + code_product = fields.Char(string='Code Product') + + @api.onchange('code_product') + def _onchange_code_product(self): + if self.code_product: + product = self.env['product.product'].search([('default_code', '=', self.code_product)], limit=1) + if not product: + product = self.env['product.product'].search([('barcode', '=', self.code_product)], limit=1) + + if product: + self.product_id = product.id + else: + raise UserError("Product tidak ditemukan") @api.depends('quantity') def _compute_status(self): @@ -1654,13 +1667,13 @@ class CheckProduct(models.Model): # Calculate the total quantity after addition total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity - if total_quantity > total_qty_in_moves: + if total_quantity == total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total - if record.quantity > total_qty_in_moves: + if record.quantity == total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) @@ -1741,6 +1754,16 @@ class ScanKoli(models.Model): string="Progress Scan Koli", compute="_compute_scan_koli_progress" ) + code_koli = fields.Char(string='Code Koli') + + @api.onchange('code_koli') + def _onchange_code_koli(self): + if self.code_koli: + koli = self.env['sales.order.koli'].search([('koli_id.koli', '=', self.code_koli)], limit=1) + if koli: + self.write({'koli_id': koli.id}) + else: + raise UserError('Koli tidak ditemukan') def _compute_scan_koli_progress(self): for scan in self: -- cgit v1.2.3 From e903492969baf706e73fca2a2a85168dfb815219 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 16 Apr 2025 14:58:43 +0700 Subject: fix bug --- indoteknik_custom/models/stock_picking.py | 34 +++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 3aa3a0a4..85428aa0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1765,14 +1765,14 @@ class ScanKoli(models.Model): else: raise UserError('Koli tidak ditemukan') - 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" + # 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): @@ -1844,13 +1844,21 @@ class ScanKoli(models.Model): def _compute_scan_koli_progress(self): for scan in self: - if scan.picking_id: + if not scan.picking_id: + scan.scan_koli_progress = "0/0" + continue + + try: 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" - + scan_index = list(all_scans).index(scan) + 1 + total_so_koli = scan.picking_id.total_so_koli or 0 + scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" + else: + scan.scan_koli_progress = "0/0" + except Exception: + # Fallback in case of any error + scan.scan_koli_progress = "0/0" @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): for scan in self.picking_id.scan_koli_lines: -- cgit v1.2.3 From 30f5cdf7b2537093d9759c49ba027ffd0608acb9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 17 Apr 2025 08:38:17 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 85428aa0..97607502 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1667,13 +1667,13 @@ class CheckProduct(models.Model): # Calculate the total quantity after addition total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity - if total_quantity == total_qty_in_moves: + if total_quantity > total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total - if record.quantity == total_qty_in_moves: + if record.quantity > total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) -- cgit v1.2.3 From de6450a17a85c063fe49705991098116efccae75 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 17 Apr 2025 10:49:27 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 97607502..85428aa0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1667,13 +1667,13 @@ class CheckProduct(models.Model): # Calculate the total quantity after addition total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity - if total_quantity > total_qty_in_moves: + if total_quantity == total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total - if record.quantity > total_qty_in_moves: + if record.quantity == total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) -- cgit v1.2.3 From fcb65326ffd7daf6136a97b53cca5493964d94aa Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 17 Apr 2025 17:06:30 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 85428aa0..f04b0e79 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1559,6 +1559,34 @@ class CheckProduct(models.Model): else: raise UserError("Product tidak ditemukan") + def unlink(self): + # Get all affected pickings before deletion + pickings = self.mapped('picking_id') + + # Store product_ids that will be deleted + deleted_product_ids = self.mapped('product_id') + + # Perform the deletion + result = super(CheckProduct, self).unlink() + + # After deletion, update moves for affected pickings + for picking in pickings: + # For products that were completely removed (no remaining check.product lines) + remaining_product_ids = picking.check_product_lines.mapped('product_id') + removed_product_ids = deleted_product_ids - remaining_product_ids + + # Set quantity_done to 0 for moves of completely removed products + moves_to_reset = picking.move_ids_without_package.filtered( + lambda move: move.product_id in removed_product_ids + ) + for move in moves_to_reset: + move.quantity_done = 0.0 + + # Also sync remaining products in case their totals changed + self._sync_check_product_to_moves(picking) + + return result + @api.depends('quantity') def _compute_status(self): for record in self: -- cgit v1.2.3 From 7545da8fdc9fc01fe9b2bd2a4612ae22d0be8e57 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 19 Apr 2025 11:59:01 +0700 Subject: add constrains date doc kirim to update calculate line no --- indoteknik_custom/models/stock_picking.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f04b0e79..923c56f5 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -248,6 +248,11 @@ class StockPicking(models.Model): final_seq = fields.Float(string='Remaining Time') shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') + + @api.constrains('date_doc_kirim') + def _constrains_date_doc_kirim(self): + for rec in self: + rec.calculate_line_no() @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): @@ -1091,7 +1096,6 @@ class StockPicking(models.Model): 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' self.final_seq = 0 -- cgit v1.2.3 From cd51ac345b0898034428aab3b8ded24d03c0fdfd Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 21 Apr 2025 15:06:14 +0700 Subject: approval invoice date --- indoteknik_custom/models/stock_picking.py | 40 ++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 923c56f5..0701a989 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -248,11 +248,40 @@ class StockPicking(models.Model): final_seq = fields.Float(string='Remaining Time') shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') + approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') @api.constrains('date_doc_kirim') def _constrains_date_doc_kirim(self): for rec in self: rec.calculate_line_no() + + invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id)], limit=1, order='create_date desc') + + if invoice: + if rec.date_doc_kirim != invoice.invoice_date: + get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) + + if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': + get_approval_invoice_date.date_doc_do = rec.date_doc_kirim + else: + approval_invoice_date = self.env['approval.invoice.date'].create({ + 'picking_id': rec.id, + 'date_invoice': invoice.invoice_date, + 'date_doc_do': rec.date_doc_kirim, + 'sale_id': rec.sale_id.id, + 'move_id': invoice.id, + 'partner_id': rec.partner_id.id + }) + + rec.approval_invoice_date_id = approval_invoice_date.id + + if approval_invoice_date: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, + } + @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): @@ -1002,6 +1031,8 @@ class StockPicking(models.Model): raise UserError('Hanya MD yang bisa Approve') def button_validate(self): + self.check_invoice_date() + threshold_datetime = waktu(2025, 4, 11, 6, 26) group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) active_model = self.env.context.get('active_model') @@ -1013,7 +1044,6 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - threshold_datetime = waktu(2025, 4, 11, 6, 26) if (len(self.konfirm_koli_lines) == 0 and 'BU/OUT/' in self.name @@ -1129,6 +1159,14 @@ class StockPicking(models.Model): self.send_mail_bills() return res + def check_invoice_date(self): + for picking in self: + invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)]) + + if invoice: + if picking.date_doc_kirim.date() != invoice.invoice_date: + raise UserError("Tanggal Kirim tidak sesuai dengan Invoice") + def set_picking_code_out(self): for picking in self: # Check if picking meets criteria -- cgit v1.2.3 From d5d073f342727e8440884b462b5d3a589894f296 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 21 Apr 2025 15:28:48 +0700 Subject: validation tanggal kirim di sj --- indoteknik_custom/models/stock_picking.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0701a989..62765099 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -250,6 +250,20 @@ class StockPicking(models.Model): state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') + def _check_date_doc_kirim_modification(self): + for record in self: + if record.date_doc_kirim: + kirim_date = fields.Datetime.from_string(record.date_doc_kirim) + now = fields.Datetime.now() + + deadline = kirim_date + timedelta(days=1) + deadline = deadline.replace(hour=10, minute=0, second=0) + + if now > deadline: + raise ValidationError( + _("Anda tidak dapat mengubah Tanggal Kirim setelah jam 10:00 pada hari berikutnya!") + ) + @api.constrains('date_doc_kirim') def _constrains_date_doc_kirim(self): for rec in self: @@ -258,6 +272,7 @@ class StockPicking(models.Model): invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id)], limit=1, order='create_date desc') if invoice: + rec._check_date_doc_kirim_modification() if rec.date_doc_kirim != invoice.invoice_date: get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) -- cgit v1.2.3 From a4abe61f52162709b7e4f9f88edfc482e092c517 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 21 Apr 2025 15:41:13 +0700 Subject: validasi compare total_mapping_koli and total_scan_koli --- indoteknik_custom/models/stock_picking.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 62765099..95591c35 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -303,6 +303,9 @@ class StockPicking(models.Model): now = datetime.datetime.utcnow() for picking in self: if len(picking.scan_koli_lines) > 0: + if len(picking.scan_koli_lines) != picking.total_mapping_koli: + raise UserError("Scan Koli Tidak Sesuai Dengan Total Mapping Koli") + picking.driver_departure_date = now @api.depends('total_so_koli') -- cgit v1.2.3 From ace97e462fc9376b041ee8b45e77f55b8d4b7837 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 21 Apr 2025 16:04:22 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 95591c35..495a9d11 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1179,11 +1179,12 @@ class StockPicking(models.Model): def check_invoice_date(self): for picking in self: - invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)]) + if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name: + invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)]) - if invoice: - if picking.date_doc_kirim.date() != invoice.invoice_date: - raise UserError("Tanggal Kirim tidak sesuai dengan Invoice") + if invoice: + if picking.date_doc_kirim.date() != invoice.invoice_date: + raise UserError("Tanggal Kirim tidak sesuai dengan Invoice") def set_picking_code_out(self): for picking in self: -- cgit v1.2.3 From 758ce73a0f2bf8cf347d8e655954677d76349027 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 21 Apr 2025 16:10:11 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 495a9d11..977b79c0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1180,11 +1180,26 @@ class StockPicking(models.Model): def check_invoice_date(self): for picking in self: if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name: - invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)]) - - if invoice: - if picking.date_doc_kirim.date() != invoice.invoice_date: - raise UserError("Tanggal Kirim tidak sesuai dengan Invoice") + continue # Skip non-relevant pickings + + invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)], limit=1) + + if not invoice: + continue # Skip if no invoice found + + # Check if both dates exist + if not picking.date_doc_kirim or not invoice.invoice_date: + raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") + + # Convert to date objects for comparison + picking_date = fields.Date.to_date(picking.date_doc_kirim) + invoice_date = fields.Date.to_date(invoice.invoice_date) + + if picking_date != invoice_date: + raise UserError("Tanggal Kirim (%s) tidak sesuai dengan Tanggal Invoice (%s)!" % ( + picking_date.strftime('%d-%m-%Y'), + invoice_date.strftime('%d-%m-%Y') + )) def set_picking_code_out(self): for picking in self: -- cgit v1.2.3 From b1f520b1b3b100deea99980b5781744411fe6936 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 22 Apr 2025 09:35:32 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 977b79c0..a860dd5d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -249,11 +249,12 @@ class StockPicking(models.Model): shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') + last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') def _check_date_doc_kirim_modification(self): for record in self: - if record.date_doc_kirim: - kirim_date = fields.Datetime.from_string(record.date_doc_kirim) + if record.last_update_date_doc_kirim: + kirim_date = fields.Datetime.from_string(record.last_update_date_doc_kirim) now = fields.Datetime.now() deadline = kirim_date + timedelta(days=1) @@ -269,9 +270,9 @@ class StockPicking(models.Model): for rec in self: rec.calculate_line_no() - invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id)], limit=1, order='create_date desc') + invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') - if invoice: + if invoice and not self.env.context.get('active_model') == 'stock.picking': rec._check_date_doc_kirim_modification() if rec.date_doc_kirim != invoice.invoice_date: get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) @@ -296,6 +297,8 @@ class StockPicking(models.Model): 'tag': 'display_notification', 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, } + + rec.last_update_date_doc_kirim = fields.Datetime.now() @api.constrains('scan_koli_lines') @@ -1180,18 +1183,16 @@ class StockPicking(models.Model): def check_invoice_date(self): for picking in self: if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name: - continue # Skip non-relevant pickings + continue invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)], limit=1) if not invoice: - continue # Skip if no invoice found + continue - # Check if both dates exist if not picking.date_doc_kirim or not invoice.invoice_date: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") - # Convert to date objects for comparison picking_date = fields.Date.to_date(picking.date_doc_kirim) invoice_date = fields.Date.to_date(invoice.invoice_date) -- cgit v1.2.3 From 19e32a22d394e0597278ee7e1c1373d7cb799ff5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 22 Apr 2025 10:10:58 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 57 ++++++++++++++++--------------- 1 file changed, 29 insertions(+), 28 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a860dd5d..95e0d59f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -270,35 +270,36 @@ class StockPicking(models.Model): for rec in self: rec.calculate_line_no() - invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') - - if invoice and not self.env.context.get('active_model') == 'stock.picking': - rec._check_date_doc_kirim_modification() - if rec.date_doc_kirim != invoice.invoice_date: - get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) - - if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': - get_approval_invoice_date.date_doc_do = rec.date_doc_kirim - else: - approval_invoice_date = self.env['approval.invoice.date'].create({ - 'picking_id': rec.id, - 'date_invoice': invoice.invoice_date, - 'date_doc_do': rec.date_doc_kirim, - 'sale_id': rec.sale_id.id, - 'move_id': invoice.id, - 'partner_id': rec.partner_id.id - }) - - rec.approval_invoice_date_id = approval_invoice_date.id - - if approval_invoice_date: - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, - } + if rec.picking_type_code == 'outgoing' and 'BU/OUT/' in rec.name: + invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') + + if invoice and not self.env.context.get('active_model') == 'stock.picking': + rec._check_date_doc_kirim_modification() + if rec.date_doc_kirim != invoice.invoice_date: + get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) + + if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': + get_approval_invoice_date.date_doc_do = rec.date_doc_kirim + else: + approval_invoice_date = self.env['approval.invoice.date'].create({ + 'picking_id': rec.id, + 'date_invoice': invoice.invoice_date, + 'date_doc_do': rec.date_doc_kirim, + 'sale_id': rec.sale_id.id, + 'move_id': invoice.id, + 'partner_id': rec.partner_id.id + }) + + rec.approval_invoice_date_id = approval_invoice_date.id + + if approval_invoice_date: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, + } - rec.last_update_date_doc_kirim = fields.Datetime.now() + rec.last_update_date_doc_kirim = fields.Datetime.now() @api.constrains('scan_koli_lines') -- cgit v1.2.3 From bdf8bf07485b01e26f61cbc75578d754af4439e8 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 22 Apr 2025 11:58:44 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 95e0d59f..96aac7a1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -270,7 +270,7 @@ class StockPicking(models.Model): for rec in self: rec.calculate_line_no() - if rec.picking_type_code == 'outgoing' and 'BU/OUT/' in rec.name: + if rec.picking_type_code == 'outgoing' and 'BU/OUT/' in rec.name and rec.partner_id.id != 96868: invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') if invoice and not self.env.context.get('active_model') == 'stock.picking': @@ -299,7 +299,7 @@ class StockPicking(models.Model): 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, } - rec.last_update_date_doc_kirim = fields.Datetime.now() + rec.last_update_date_doc_kirim = datetime.datetime.utcnow() @api.constrains('scan_koli_lines') @@ -1183,7 +1183,7 @@ class StockPicking(models.Model): def check_invoice_date(self): for picking in self: - if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name: + if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name or picking.partner_id.id == 96868: continue invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)], limit=1) -- cgit v1.2.3 From 9e867f016484f8695fce9e0c313d41010434390c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 22 Apr 2025 15:01:28 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 96aac7a1..cd038f44 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -26,11 +26,11 @@ _biteship_api_key = "biteship_test.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) + konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True, copy=False) + scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True, copy=False) + check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True, copy=False) - check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True) + check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, copy=False) 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') account_id = fields.Many2one('account.account', string='Account') @@ -100,7 +100,7 @@ class StockPicking(models.Model): ('pengajuan1', 'Approval Finance'), ('approved', 'Approved'), ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return") - date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True) + date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, copy=False) note_logistic = fields.Selection([ ('hold', 'Hold by Sales'), ('not_paid', 'Customer belum bayar'), @@ -154,8 +154,8 @@ class StockPicking(models.Model): # record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih" quantity_koli = fields.Float(string="Quantity Koli", copy=False) total_mapping_koli = fields.Float(string="Total Mapping Koli", compute='_compute_total_mapping_koli') - so_lama = fields.Boolean('SO LAMA') - linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out') + so_lama = fields.Boolean('SO LAMA', copy=False) + linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) # def write(self, vals): # if 'linked_manual_bu_out' in vals: -- cgit v1.2.3 From 1981b2d576d916374c181c8089655ab59a3f7f20 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 23 Apr 2025 14:29:06 +0700 Subject: barcode box --- indoteknik_custom/models/stock_picking.py | 49 +++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 13 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index cd038f44..6168d3b2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1621,21 +1621,44 @@ class CheckProduct(models.Model): copy=False, ) product_id = fields.Many2one('product.product', string='Product') - quantity = fields.Float(string='Quantity', default=1.0) + quantity = fields.Float(string='Quantity') status = fields.Char(string='Status', compute='_compute_status') code_product = fields.Char(string='Code Product') @api.onchange('code_product') def _onchange_code_product(self): - if self.code_product: - product = self.env['product.product'].search([('default_code', '=', self.code_product)], limit=1) - if not product: - product = self.env['product.product'].search([('barcode', '=', self.code_product)], limit=1) - - if product: - self.product_id = product.id - else: - raise UserError("Product tidak ditemukan") + if not self.code_product: + return + + # Cari product berdasarkan default_code, barcode, atau barcode_box + product = self.env['product.product'].search([ + '|', + ('default_code', '=', self.code_product), + '|', + ('barcode', '=', self.code_product), + ('barcode_box', '=', self.code_product) + ], limit=1) + + if not product: + raise UserError("Product tidak ditemukan") + + # Jika scan barcode_box, set quantity sesuai qty_pcs_box + if product.barcode_box == self.code_product: + self.product_id = product.id + self.quantity = product.qty_pcs_box + self.code_product = product.default_code or product.barcode + # return { + # 'warning': { + # 'title': 'Info',8994175025871 + + # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}' + # } + # } + else: + # Jika scan biasa + self.product_id = product.id + self.code_product = product.default_code or product.barcode + self.quantity = 1 def unlink(self): # Get all affected pickings before deletion @@ -1763,7 +1786,7 @@ class CheckProduct(models.Model): # Find existing lines for the same product, excluding the current line existing_lines = record.picking_id.check_product_lines.filtered( - lambda line: line.product_id == record.product_id and line.id != record.id + lambda line: line.product_id == record.product_id ) if existing_lines: @@ -1771,9 +1794,9 @@ class CheckProduct(models.Model): first_line = existing_lines[0] # Calculate the total quantity after addition - total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity + total_quantity = sum(existing_lines.mapped('quantity')) - if total_quantity == total_qty_in_moves: + if total_quantity > total_qty_in_moves: raise UserError(( "Quantity Product '%s' sudah melebihi quantity demand." ) % (record.product_id.display_name)) -- cgit v1.2.3 From 4706b80d3d3b1e55c198d2b4cfb93f7fa47c9732 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 13:49:35 +0700 Subject: validation duplicate barcode product and barcode box, cr date doc kirim, validation duplicate product id on so line --- indoteknik_custom/models/stock_picking.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6168d3b2..f812df86 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -102,10 +102,9 @@ class StockPicking(models.Model): ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return") date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, copy=False) note_logistic = fields.Selection([ - ('hold', 'Hold by Sales'), + ('wait_so_together', 'Tunggu SO Barengan'), ('not_paid', 'Customer belum bayar'), - ('partial', 'Kirim Parsial'), - ('indent', 'Indent'), + ('reserve_stock', 'Reserve Stock'), ('waiting_schedule', 'Menunggu Jadwal Kirim'), ('self_pickup', 'Barang belum di pickup Customer'), ('expedition_closed', 'Eskpedisi belum buka') @@ -141,7 +140,8 @@ class StockPicking(models.Model): ('done', 'Done'), ('cancel', 'Cancelled'), ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") - notee = fields.Text(string="Note") + notee = fields.Text(string="Note SJ", help="Catatan untuk kirim barang") + note_info = fields.Text(string="Note", help="Catatan untuk pengiriman") state_approve_md = fields.Selection([ ('waiting', 'Waiting For Approve by MD'), ('pending', 'Pending (perlu koordinasi dengan MD)'), @@ -253,7 +253,7 @@ class StockPicking(models.Model): def _check_date_doc_kirim_modification(self): for record in self: - if record.last_update_date_doc_kirim: + if record.last_update_date_doc_kirim and not self.env.context.get('from_button_approve'): kirim_date = fields.Datetime.from_string(record.last_update_date_doc_kirim) now = fields.Datetime.now() @@ -275,7 +275,7 @@ class StockPicking(models.Model): if invoice and not self.env.context.get('active_model') == 'stock.picking': rec._check_date_doc_kirim_modification() - if rec.date_doc_kirim != invoice.invoice_date: + if rec.date_doc_kirim != invoice.invoice_date and not self.env.context.get('from_button_approve'): get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': -- cgit v1.2.3 From add5cafc05aec0036fa1382c90dbf3abc8e1ecf5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 13:52:08 +0700 Subject: add validation barcode product --- indoteknik_custom/models/stock_picking.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f812df86..a8a4a4e6 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1826,9 +1826,21 @@ class BarcodeProduct(models.Model): product_id = fields.Many2one('product.product', string='Product', required=True) barcode = fields.Char(string='Barcode') + def check_duplicate_barcode(self): + barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)]) + + if barcode_product: + raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) + + barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) + + if barcode_box: + raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name)) + @api.constrains('barcode') def send_barcode_to_product(self): for record in self: + record.check_duplicate_barcode() if record.barcode and not record.product_id.barcode: record.product_id.barcode = record.barcode else: -- cgit v1.2.3 From 21653f522154475d9029c1e2b395960b58ecf47a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 24 Apr 2025 13:56:11 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a8a4a4e6..8f8ab54d 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -141,7 +141,7 @@ class StockPicking(models.Model): ('cancel', 'Cancelled'), ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") notee = fields.Text(string="Note SJ", help="Catatan untuk kirim barang") - note_info = fields.Text(string="Note", help="Catatan untuk pengiriman") + note_info = fields.Text(string="Note Logistix (Text)", help="Catatan untuk pengiriman") state_approve_md = fields.Selection([ ('waiting', 'Waiting For Approve by MD'), ('pending', 'Pending (perlu koordinasi dengan MD)'), -- cgit v1.2.3 From d6516bce4ac05a25baf060d5341f7b603961496f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 25 Apr 2025 10:39:05 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 8f8ab54d..e0ae8258 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1191,7 +1191,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.date_doc_kirim or not invoice.invoice_date: + if not picking.date_doc_kirim or not invoice.invoice_date and not picking.so_lama: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From 6dfa0b1ec5c9fd60f2adee26b7dbb1de3aea5302 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 25 Apr 2025 16:11:46 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index e0ae8258..64dc1499 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -250,6 +250,7 @@ class StockPicking(models.Model): state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') + update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') def _check_date_doc_kirim_modification(self): for record in self: @@ -1197,7 +1198,7 @@ class StockPicking(models.Model): picking_date = fields.Date.to_date(picking.date_doc_kirim) invoice_date = fields.Date.to_date(invoice.invoice_date) - if picking_date != invoice_date: + if picking_date != invoice_date and picking.update_date_doc_kirim_add: raise UserError("Tanggal Kirim (%s) tidak sesuai dengan Tanggal Invoice (%s)!" % ( picking_date.strftime('%d-%m-%Y'), invoice_date.strftime('%d-%m-%Y') -- cgit v1.2.3 From 8b532b193a47c6be35bcfb49b77849458a379cee Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 28 Apr 2025 09:34:12 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 64dc1499..a6b3373f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1192,7 +1192,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.date_doc_kirim or not invoice.invoice_date and not picking.so_lama: + if picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From bac1744ce4e27d796fd2b52f5fbcd3d5cdabdc75 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 28 Apr 2025 09:35:23 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a6b3373f..d2b1b9f2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1192,7 +1192,7 @@ class StockPicking(models.Model): if not invoice: continue - if picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: + if not picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From 475173ec46981926aa90e99f796e38ef1d46b61e Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 10:15:35 +0700 Subject: sequence pricelist and fix bug validation invoice date and date doc kirim --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index d2b1b9f2..0b688fab 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1187,7 +1187,7 @@ class StockPicking(models.Model): if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name or picking.partner_id.id == 96868: continue - invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id)], limit=1) + invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id), ('state','not in',['draft','cancel'])], limit=1) if not invoice: continue -- cgit v1.2.3 From 6f93b7a7a47481f6f308295a49bce40505041411 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 30 Apr 2025 14:55:49 +0700 Subject: (andri) add field area (kecamatan & kota) pada stock picking --- indoteknik_custom/models/stock_picking.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0b688fab..ccb551b0 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,6 +157,15 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) + area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) + + @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + def _compute_area_name(self): + for record in self: + district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' + city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + record.area_name = f"{district}, {city}".strip(', ') + # def write(self, vals): # if 'linked_manual_bu_out' in vals: # for record in self: -- cgit v1.2.3 From a73856faaa1999b0c5ec20c41b37fda8a8eed31a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 30 Apr 2025 15:13:52 +0700 Subject: (andri) add area (kecamatan kota) pada stock picking --- indoteknik_custom/models/stock_picking.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 64dc1499..75803ccb 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,6 +157,14 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) + area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) + @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + def _compute_area_name(self): + for record in self: + district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' + city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + record.area_name = f"{district}, {city}".strip(', ') + # def write(self, vals): # if 'linked_manual_bu_out' in vals: # for record in self: -- cgit v1.2.3 From de1f159b50d42942381c0c7ab28e600b091c22ca Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 30 Apr 2025 17:22:17 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f71c07e0..c35c3047 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,12 +157,12 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) - area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) - @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + area_name = fields.Char(string="Area", compute="_compute_area_name") + # @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') def _compute_area_name(self): for record in self: - district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' - city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + district = record.real_shipping_id.kelurahan_id.name if record.real_shipping_id.kelurahan_id else '' + city = record.real_shipping_id.kota_id.name if record.real_shipping_id.kota_id else '' record.area_name = f"{district}, {city}".strip(', ') # def write(self, vals): -- cgit v1.2.3 From bb8a5981bdfd8a8d60db7fe509a989c29b2a4e5e Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 2 May 2025 15:49:43 +0700 Subject: (andri) rev area list picking --- indoteknik_custom/models/stock_picking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ccb551b0..e178ad1c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -157,13 +157,13 @@ class StockPicking(models.Model): so_lama = fields.Boolean('SO LAMA', copy=False) linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) - area_name = fields.Char(string="Area", compute="_compute_area_name", store=True) + area_name = fields.Char(string="Area", compute="_compute_area_name") - @api.depends('real_shipping_id.district_id_pengiriman', 'real_shipping_id.city_id_pengiriman') + @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): for record in self: - district = record.real_shipping_id.district_id_pengiriman.name if record.real_shipping_id.district_id_pengiriman else '' - city = record.real_shipping_id.city_id_pengiriman.name if record.real_shipping_id.city_id_pengiriman else '' + district = record.real_shipping_id.kecamatan_id or '' + city = record.real_shipping_id.kota_id or '' record.area_name = f"{district}, {city}".strip(', ') # def write(self, vals): -- cgit v1.2.3 From a91ffe639f73b8feac0e933524e92ed3e4168e69 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 May 2025 17:09:02 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a017a090..b253090a 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1201,7 +1201,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.so_lama and not picking.date_doc_kirim or not invoice.invoice_date: + if not picking.so_lama and (not picking.date_doc_kirim or not invoice.invoice_date): raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From 7f6671fba5d872cfd0d80efadf667e039b95b0c5 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Sat, 3 May 2025 09:54:51 +0700 Subject: push area --- indoteknik_custom/models/stock_picking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index b253090a..1291737e 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -161,8 +161,8 @@ class StockPicking(models.Model): @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): for record in self: - district = record.real_shipping_id.kecamatan_id or '' - city = record.real_shipping_id.kota_id or '' + district = record.real_shipping_id.kecamatan_id.name or '' + city = record.real_shipping_id.kota_id.name or '' record.area_name = f"{district}, {city}".strip(', ') -- cgit v1.2.3 From 982e2b966385965328aafaa8607ca31d811874d9 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 5 May 2025 10:43:10 +0700 Subject: date reserved bu/out = validate date bu/pick --- indoteknik_custom/models/stock_picking.py | 505 +++++++++++++++++------------- 1 file changed, 279 insertions(+), 226 deletions(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1291737e..ce1399fe 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -15,22 +15,26 @@ import requests import time import logging import re + _logger = logging.getLogger(__name__) _biteship_url = "https://api.biteship.com/v1" _biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA" + + # _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo" - - + 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, copy=False) + konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True, + copy=False) scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True, copy=False) check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True, copy=False) - - check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, copy=False) + + check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, + copy=False) 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') account_id = fields.Many2one('account.account', string='Account') @@ -89,18 +93,23 @@ class StockPicking(models.Model): approval_status = fields.Selection([ ('pengajuan1', 'Approval Accounting'), ('approved', 'Approved'), - ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Internal Use") + ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3, + help="Approval Status untuk Internal Use") approval_receipt_status = fields.Selection([ ('pengajuan1', 'Approval Logistic'), ('approved', 'Approved'), - ], string='Approval Receipt Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Receipt") + ], string='Approval Receipt Status', readonly=True, copy=False, index=True, tracking=3, + help="Approval Status untuk Receipt") approval_return_status = fields.Selection([ ('pengajuan1', 'Approval Finance'), ('approved', 'Approved'), - ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return") - date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, copy=False) + ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, + help="Approval Status untuk Return") + date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', + help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True, + copy=False) note_logistic = fields.Selection([ ('wait_so_together', 'Tunggu SO Barengan'), ('not_paid', 'Customer belum bayar'), @@ -110,7 +119,8 @@ class StockPicking(models.Model): ('expedition_closed', 'Eskpedisi belum buka') ], string='Note Logistic', help='jika field ini diisi maka tidak akan dihitung ke lead time') waybill_id = fields.One2many(comodel_name='airway.bill', inverse_name='do_id', string='Airway Bill') - purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id', string="Purchase Representative") + purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id', + string="Purchase Representative") carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method') shipping_status = fields.Char(string='Shipping Status', compute="_compute_shipping_status") date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False) @@ -131,9 +141,9 @@ class StockPicking(models.Model): ('invoiced', 'Fully Invoiced'), ('to invoice', 'To Invoice'), ('no', 'Nothing to Invoice') - ], string='Invoice Status', related="sale_id.invoice_status") + ], string='Invoice Status', related="sale_id.invoice_status") note_return = fields.Text(string="Note Return", help="Catatan untuk kirim barang kembali") - + state_reserve = fields.Selection([ ('waiting', 'Waiting For Fullfilment'), ('ready', 'Ready to Ship'), @@ -146,7 +156,8 @@ class StockPicking(models.Model): ('waiting', 'Waiting For Approve by MD'), ('pending', 'Pending (perlu koordinasi dengan MD)'), ('done', 'Approve by MD'), - ], string='Approval MD Gudang Selisih', tracking=True, copy=False, help="The current state of the MD Approval transfer barang from gudang selisih.") + ], string='Approval MD Gudang Selisih', tracking=True, copy=False, + help="The current state of the MD Approval transfer barang from gudang selisih.") # show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md") # def _compute_show_state_approve_md(self): @@ -158,13 +169,13 @@ class StockPicking(models.Model): linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False) area_name = fields.Char(string="Area", compute="_compute_area_name") + @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id') def _compute_area_name(self): for record in self: district = record.real_shipping_id.kecamatan_id.name or '' city = record.real_shipping_id.kota_id.name or '' record.area_name = f"{district}, {city}".strip(', ') - # def write(self, vals): # if 'linked_manual_bu_out' in vals: @@ -189,7 +200,7 @@ class StockPicking(models.Model): # picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out']) # picking.state_packing = 'packing_done' # return record - + @api.depends('konfirm_koli_lines', 'konfirm_koli_lines.pick_id', 'konfirm_koli_lines.pick_id.quantity_koli') def _compute_total_mapping_koli(self): for record in self: @@ -209,8 +220,10 @@ class StockPicking(models.Model): for picking in self: picking.dokumen_pengiriman = picking.partner_id.dokumen_pengiriman_input - dokumen_tanda_terima = fields.Char(string='Dokumen Tanda Terima yang Diberikan Pada Saat Pengiriman Barang', readonly=True, compute=_compute_dokumen_tanda_terima) - dokumen_pengiriman = fields.Char(string='Dokumen yang Dibawa Saat Pengiriman Barang', readonly=True, compute=_compute_dokumen_pengiriman) + dokumen_tanda_terima = fields.Char(string='Dokumen Tanda Terima yang Diberikan Pada Saat Pengiriman Barang', + readonly=True, compute=_compute_dokumen_tanda_terima) + dokumen_pengiriman = fields.Char(string='Dokumen yang Dibawa Saat Pengiriman Barang', readonly=True, + compute=_compute_dokumen_pengiriman) # Envio Tracking Section envio_id = fields.Char(string="Envio ID", readonly=True) @@ -255,8 +268,10 @@ class StockPicking(models.Model): # countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False) # 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') - shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', related='sale_id.carrier_id') - state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], string='Packing Status') + shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO', + related='sale_id.carrier_id') + state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')], + string='Packing Status') approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date') last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') @@ -266,10 +281,10 @@ class StockPicking(models.Model): if record.last_update_date_doc_kirim and not self.env.context.get('from_button_approve'): kirim_date = fields.Datetime.from_string(record.last_update_date_doc_kirim) now = fields.Datetime.now() - + deadline = kirim_date + timedelta(days=1) deadline = deadline.replace(hour=10, minute=0, second=0) - + if now > deadline: raise ValidationError( _("Anda tidak dapat mengubah Tanggal Kirim setelah jam 10:00 pada hari berikutnya!") @@ -281,12 +296,15 @@ class StockPicking(models.Model): rec.calculate_line_no() if rec.picking_type_code == 'outgoing' and 'BU/OUT/' in rec.name and rec.partner_id.id != 96868: - invoice = self.env['account.move'].search([('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], limit=1, order='create_date desc') + invoice = self.env['account.move'].search( + [('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')], + limit=1, order='create_date desc') if invoice and not self.env.context.get('active_model') == 'stock.picking': rec._check_date_doc_kirim_modification() if rec.date_doc_kirim != invoice.invoice_date and not self.env.context.get('from_button_approve'): - get_approval_invoice_date = self.env['approval.invoice.date'].search([('picking_id', '=', rec.id),('state', '=', 'draft')], limit=1) + get_approval_invoice_date = self.env['approval.invoice.date'].search( + [('picking_id', '=', rec.id), ('state', '=', 'draft')], limit=1) if get_approval_invoice_date and get_approval_invoice_date.state == 'draft': get_approval_invoice_date.date_doc_do = rec.date_doc_kirim @@ -306,12 +324,13 @@ class StockPicking(models.Model): return { 'type': 'ir.actions.client', 'tag': 'display_notification', - 'params': { 'title': 'Notification', 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', 'next': {'type': 'ir.actions.act_window_close'} }, + 'params': {'title': 'Notification', + 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat', + 'next': {'type': 'ir.actions.act_window_close'}}, } - + rec.last_update_date_doc_kirim = datetime.datetime.utcnow() - - + @api.constrains('scan_koli_lines') def _constrains_scan_koli_lines(self): now = datetime.datetime.utcnow() @@ -319,16 +338,18 @@ class StockPicking(models.Model): if len(picking.scan_koli_lines) > 0: if len(picking.scan_koli_lines) != picking.total_mapping_koli: raise UserError("Scan Koli Tidak Sesuai Dengan Total Mapping Koli") - + picking.driver_departure_date = now @api.depends('total_so_koli') def _compute_total_so_koli(self): for picking in self: 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')]) + 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')]) + 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): @@ -356,21 +377,21 @@ class StockPicking(models.Model): 'koli_id': rec.id, }) else: - raise UserError('Tidak Bisa Mengubah Quantity Koli Karena Koli Dari Picking Ini Sudah Dipakai Di BU/OUT!') + 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)] self.check_koli_lines = [(0, 0, { - 'koli': f"{self.name}/{str(i+1).zfill(3)}", + 'koli': f"{self.name}/{str(i + 1).zfill(3)}", 'picking_id': self.id, }) for i in range(int(self.quantity_koli))] - + def schduled_update_sequance(self): 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: @@ -379,9 +400,9 @@ class StockPicking(models.Model): # rts = record.estimated_ready_ship_date - waktu.now() # rts_days = rts.days # rts_hours = divmod(rts.seconds, 3600) - + # estimated_by_erts = rts.total_seconds() / 3600 - + # record.countdown_ready_to_ship = f"{rts_days} days, {rts_hours} hours" # record.countdown_hours = estimated_by_erts # else: @@ -391,7 +412,6 @@ class StockPicking(models.Model): # _logger.error(f"Error calculating sequance {record.id}: {str(e)}") # print(str(e)) # return { 'error': str(e) } - # @api.depends('estimated_ready_ship_date', 'state') # def _compute_countdown_hours(self): @@ -431,7 +451,7 @@ class StockPicking(models.Model): ('state', '=', 'done'), ('carrier_id', '=', 9), ('lalamove_order_id', '!=', False) - ]) + ]) for picking in pickings: try: order_id = picking.lalamove_order_id @@ -466,7 +486,7 @@ class StockPicking(models.Model): for stop in stops: pod = stop.get("POD", {}) if pod.get("status") == "DELIVERED": - image_url = pod.get("image") # Sesuaikan jika key berbeda + image_url = pod.get("image") # Sesuaikan jika key berbeda self.lalamove_image_url = image_url address = stop.get("address") @@ -487,7 +507,6 @@ class StockPicking(models.Model): else: raise UserError(f"Error {response.status_code}: {response.text}") - def _convert_to_wib(self, date_str): """ Mengonversi string waktu ISO 8601 ke format waktu Indonesia (WIB) @@ -573,7 +592,7 @@ class StockPicking(models.Model): picking.tracking_by = self.env.user.id ata_at_str = data.get("ata_at") envio_ata = self._convert_to_datetime(data.get("ata_at")) - + picking.driver_arrival_date = envio_ata if data.get("status") != 'delivered': picking.driver_arrival_date = False @@ -584,13 +603,13 @@ class StockPicking(models.Model): raise UserError(f"Kesalahan tidak terduga: {str(e)}") def action_send_to_biteship(self): - + if self.biteship_tracking_id: raise UserError(f"Order ini sudah dikirim ke Biteship. Dengan Tracking Id: {self.biteship_tracking_id}") - + # Mencari data sale.order.line berdasarkan sale_id products = self.env['sale.order.line'].search([('order_id', '=', self.sale_id.id)]) - + # Fungsi untuk membangun items_data dari order lines def build_items_data(lines): return [{ @@ -612,7 +631,7 @@ class StockPicking(models.Model): ('order_id', '=', self.sale_id.id), ('product_id', '=', move_line.product_id.id) ], limit=1) - + if order_line: items_data_instant.append({ "name": order_line.product_id.name, @@ -623,7 +642,7 @@ class StockPicking(models.Model): }) payload = { - "reference_id " : self.sale_id.name, + "reference_id ": self.sale_id.name, "shipper_contact_name": self.carrier_id.pic_name or '', "shipper_contact_phone": self.carrier_id.pic_phone or '', "shipper_organization": self.carrier_id.name, @@ -644,19 +663,20 @@ class StockPicking(models.Model): } # Cek jika pengiriman instant atau same_day - if self.sale_id.delivery_service_type and ("instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): + if self.sale_id.delivery_service_type and ( + "instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type): payload.update({ - "origin_coordinate" :{ + "origin_coordinate": { "latitude": -6.3031123, - "longitude" : 106.7794934999 + "longitude": 106.7794934999 }, - "destination_coordinate" : { + "destination_coordinate": { "latitude": self.real_shipping_id.latitude, "longitude": self.real_shipping_id.longtitude, }, "items": items_data_instant }) - + api_key = _biteship_api_key headers = { "Authorization": f"Bearer {api_key}", @@ -664,7 +684,7 @@ class StockPicking(models.Model): } # Kirim request ke Biteship - response = requests.post(_biteship_url+'/orders', headers=headers, json=payload) + response = requests.post(_biteship_url + '/orders', headers=headers, json=payload) if response.status_code == 200: data = response.json() @@ -673,7 +693,7 @@ class StockPicking(models.Model): self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "") self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "") self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "") - + waybill_id = data.get("courier", {}).get("waybill_id", "") message = f"✅ Berhasil Order ke Biteship! Resi: {waybill_id}" if waybill_id else "⚠️ Order berhasil, tetapi tidak ada nomor resi." @@ -690,10 +710,10 @@ class StockPicking(models.Model): error_message = error_data.get("error", "Unknown error") error_code = error_data.get("code", "No code provided") raise UserError(f"Error saat mengirim ke Biteship: {error_message} (Code: {error_code})") - + @api.constrains('driver_departure_date') - def constrains_driver_departure_date(self): - if not self.date_doc_kirim: + def constrains_driver_departure_date(self): + if not self.date_doc_kirim: self.date_doc_kirim = self.driver_departure_date @api.constrains('arrival_time') @@ -721,32 +741,32 @@ class StockPicking(models.Model): if not self._context.get('darimana') == 'sale.order' and self.env.user.id not in users_in_group.mapped('id'): self.sale_id.unreserve_id = self.id return self._create_approval_notification('Logistic') - + res = super(StockPicking, self).do_unreserve() current_time = datetime.datetime.utcnow() self.date_unreserve = current_time - + return res - + def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'internal'), ('name', 'ilike', 'BU/PICK/'), ]) - + for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ ('sale_order_id', '=', picking.sale_id.id) ]) - + picking.state_reserve = 'ready' picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() - + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): picking.state_reserve = 'waiting' picking.date_reserved = '' - + self.check_state_reserve_backorder() def check_state_reserve_backorder(self): @@ -756,29 +776,29 @@ class StockPicking(models.Model): ('picking_type_code', '=', 'internal'), ('state', 'not in', ['cancel', 'draft', 'done']) ]) - + for picking in pickings: fullfillments = self.env['sales.order.fulfillment.v2'].search([ ('sale_order_id', '=', picking.sale_id.id) ]) - + picking.state_reserve = 'ready' picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() - + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): picking.state_reserve = 'waiting' picking.date_reserved = '' - + def _create_approval_notification(self, approval_role): title = 'Warning' message = f'Butuh approval sales untuk unreserved' return self._create_notification_action(title, message) - + def _create_notification_action(self, title, message): return { 'type': 'ir.actions.client', 'tag': 'display_notification', - 'params': { 'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'} }, + 'params': {'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'}}, } def _compute_shipping_status(self): @@ -788,7 +808,7 @@ class StockPicking(models.Model): status = 'shipment' elif rec.driver_departure_date and (rec.sj_return_date or rec.driver_arrival_date): status = 'completed' - + rec.shipping_status = status def action_create_invoice_from_mr(self): @@ -796,10 +816,10 @@ class StockPicking(models.Model): """ if not self.env.user.is_accounting: raise UserError('Hanya Accounting yang bisa membuat Bill') - + precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') - #custom here + # custom here po = self.env['purchase.order'].search([ ('name', '=', self.group_id.name) ]) @@ -816,24 +836,29 @@ class StockPicking(models.Model): invoice_vals = order._prepare_invoice() # Invoice line values (keep only necessary sections). for line in self.move_ids_without_package: - po_line = self.env['purchase.order.line'].search([('order_id', '=', po.id), ('product_id', '=', line.product_id.id)], limit=1) + po_line = self.env['purchase.order.line'].search( + [('order_id', '=', po.id), ('product_id', '=', line.product_id.id)], limit=1) qty = line.product_uom_qty if po_line.display_type == 'line_section': pending_section = line continue if not float_is_zero(po_line.qty_to_invoice, precision_digits=precision): if pending_section: - invoice_vals['invoice_line_ids'].append((0, 0, pending_section._prepare_account_move_line_from_mr(po_line, qty))) + invoice_vals['invoice_line_ids'].append( + (0, 0, pending_section._prepare_account_move_line_from_mr(po_line, qty))) pending_section = None - invoice_vals['invoice_line_ids'].append((0, 0, line._prepare_account_move_line_from_mr(po_line, qty))) + invoice_vals['invoice_line_ids'].append( + (0, 0, line._prepare_account_move_line_from_mr(po_line, qty))) invoice_vals_list.append(invoice_vals) if not invoice_vals_list: - raise UserError(_('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.')) + raise UserError( + _('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.')) # 2) group by (company_id, partner_id, currency_id) for batch creation new_invoice_vals_list = [] - for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: (x.get('company_id'), x.get('partner_id'), x.get('currency_id'))): + for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: ( + x.get('company_id'), x.get('partner_id'), x.get('currency_id'))): origins = set() payment_refs = set() refs = set() @@ -863,7 +888,8 @@ class StockPicking(models.Model): # 4) Some moves might actually be refunds: convert them if the total amount is negative # We do this after the moves have been created since we need taxes, etc. to know if the total # is actually negative or not - moves.filtered(lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_invoice_into_refund_credit_note() + moves.filtered( + lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_invoice_into_refund_credit_note() return self.action_view_invoice_from_mr(moves) @@ -926,7 +952,7 @@ class StockPicking(models.Model): # for stock_move_line in stock_move_lines: # if stock_move_line.picking_id.state not in list_state: # continue - # raise UserError('Sudah pernah dikirim kalender') + # raise UserError('Sudah pernah dikirim kalender') for pick in self: if not pick.is_internal_use: @@ -952,18 +978,20 @@ class StockPicking(models.Model): if self.picking_type_code == 'outgoing': if self.env.user.id in [3988, 3401, 20] or ( - self.env.user.has_group('indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin + self.env.user.has_group( + 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin ): action['context'] = {'picking_ids': [x.id for x in self]} return action - elif not self.env.user.has_group('indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin: + elif not self.env.user.has_group( + 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin: raise UserError('Harus Purchasing yang Ask Return') else: raise UserError('Harus Sales Admin yang Ask Return') elif self.picking_type_code == 'incoming': if self.env.user.has_group('indoteknik_custom.group_role_purchasing') or ( - self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin + self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin ): action['context'] = {'picking_ids': [x.id for x in self]} return action @@ -973,7 +1001,7 @@ class StockPicking(models.Model): raise UserError('Harus Purchasing yang Ask Return') def calculate_line_no(self): - + for picking in self: name = picking.group_id.name for move in picking.move_ids_without_package: @@ -996,10 +1024,10 @@ class StockPicking(models.Model): def _compute_summary_qty(self): for picking in self: sum_qty_detail = sum_qty_operation = count_line_detail = count_line_operation = 0 - for detail in picking.move_line_ids_without_package: # detailed operations + for detail in picking.move_line_ids_without_package: # detailed operations sum_qty_detail += detail.qty_done count_line_detail += 1 - for operation in picking.move_ids_without_package: # operations + for operation in picking.move_ids_without_package: # operations sum_qty_operation += operation.product_uom_qty count_line_operation += 1 picking.summary_qty_detail = sum_qty_detail @@ -1021,13 +1049,13 @@ class StockPicking(models.Model): ]) if ( - self.picking_type_id.id == 29 - and quant - and line.location_id.id == bu_location_id - and quant.inventory_quantity < line.product_uom_qty + self.picking_type_id.id == 29 + and quant + and line.location_id.id == bu_location_id + and quant.inventory_quantity < line.product_uom_qty ): raise UserError('Quantity reserved lebih besar dari quantity onhand di product') - + def check_qty_done_stock(self): for line in self.move_line_ids_without_package: def check_qty_per_inventory(self, product, location): @@ -1040,10 +1068,11 @@ class StockPicking(models.Model): return quant.quantity return 0 - + qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id) if line.qty_done > qty_onhand: raise UserError('Quantity Done melebihi Quantity Onhand') + def button_state_approve_md(self): group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) @@ -1068,7 +1097,8 @@ class StockPicking(models.Model): group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) active_model = self.env.context.get('active_model') - if self.location_id.id == 47 and self.env.user.id not in users_in_group.mapped('id') and self.state_approve_md != 'done': + if self.location_id.id == 47 and self.env.user.id not in users_in_group.mapped( + 'id') and self.state_approve_md != 'done': self.state_approve_md = 'waiting' if self.state_approve_md != 'pending' else 'pending' self.env.cr.commit() raise UserError("Transfer dari gudang selisih harus di approve MD, Hubungi MD agar bisa di Validate") @@ -1076,37 +1106,36 @@ class StockPicking(models.Model): if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'): self.state_approve_md = 'done' - - if (len(self.konfirm_koli_lines) == 0 - and 'BU/OUT/' in self.name - and self.picking_type_code == 'outgoing' - and self.create_date > threshold_datetime - and not self.so_lama): + if (len(self.konfirm_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and self.create_date > threshold_datetime + and not self.so_lama): 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' - and self.create_date > threshold_datetime - and not self.so_lama): + if (len(self.scan_koli_lines) == 0 + and 'BU/OUT/' in self.name + and self.picking_type_code == 'outgoing' + and self.create_date > threshold_datetime + and not self.so_lama): 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 not self.linked_manual_bu_out and 'BU/PICK/' in self.name: raise UserError(_("Isi BU Out terlebih dahulu!")) - + if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name: raise UserError(_("Tidak ada Check Product! 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.t1otal_so_koli)) - + raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.") + % (self.total_koli, self.t1otal_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") @@ -1128,7 +1157,7 @@ class StockPicking(models.Model): if self.is_internal_use and not self.env.user.is_accounting: raise UserError("Harus di Approve oleh Accounting") - + if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: raise UserError("Harus di Approve oleh Logistik") @@ -1163,6 +1192,16 @@ class StockPicking(models.Model): self.final_seq = 0 self.set_picking_code_out() self.send_koli_to_so() + + if (self.state_reserve == 'done' and self.picking_type_code == 'internal' and 'BU/PICK/' in self.name + and self.linked_manual_bu_out): + if not self.linked_manual_bu_out.date_reserved: + current_datetime = datetime.datetime.utcnow() + self.linked_manual_bu_out.date_reserved = current_datetime + self.linked_manual_bu_out.message_post( + body=f"Date Reserved diisi secara otomatis dari validasi BU/PICK {self.name}" + ) + if not self.env.context.get('skip_koli_check'): for picking in self: if picking.sale_id: @@ -1172,7 +1211,8 @@ class StockPicking(models.Model): 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_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 @@ -1190,36 +1230,37 @@ class StockPicking(models.Model): } self.send_mail_bills() return res - + def check_invoice_date(self): for picking in self: if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name or picking.partner_id.id == 96868: continue - - invoice = self.env['account.move'].search([('sale_id', '=', picking.sale_id.id), ('state','not in',['draft','cancel'])], limit=1) - + + invoice = self.env['account.move'].search( + [('sale_id', '=', picking.sale_id.id), ('state', 'not in', ['draft', 'cancel'])], limit=1) + if not invoice: continue - + if not picking.so_lama and (not picking.date_doc_kirim or not invoice.invoice_date): raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") - + picking_date = fields.Date.to_date(picking.date_doc_kirim) invoice_date = fields.Date.to_date(invoice.invoice_date) - + if picking_date != invoice_date and picking.update_date_doc_kirim_add: raise UserError("Tanggal Kirim (%s) tidak sesuai dengan Tanggal Invoice (%s)!" % ( picking_date.strftime('%d-%m-%Y'), invoice_date.strftime('%d-%m-%Y') )) - + def set_picking_code_out(self): for picking in self: # Check if picking meets criteria is_bu_pick = picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name if not is_bu_pick: continue - + # Find matching outgoing transfers bu_out_transfers = self.search([ ('name', 'like', 'BU/OUT/%'), @@ -1228,11 +1269,10 @@ class StockPicking(models.Model): ('picking_code', '=', False), ('state', 'not in', ['done', 'cancel']) ]) - + # Assign sequence code to each matching transfer for transfer in bu_out_transfers: transfer.picking_code = self.env['ir.sequence'].next_by_code('stock.picking.code') - def check_koli(self): for picking in self: @@ -1240,7 +1280,7 @@ class StockPicking(models.Model): 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: if picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name: @@ -1257,7 +1297,7 @@ 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: if picking.state == 'done': for koli_line in picking.scan_koli_lines: @@ -1265,7 +1305,7 @@ class StockPicking(models.Model): ('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): @@ -1280,7 +1320,7 @@ class StockPicking(models.Model): return quant.quantity return 0 - + qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id) if line.qty_done > qty_onhand: raise UserError('Quantity Done melebihi Quantity Onhand') @@ -1337,11 +1377,14 @@ class StockPicking(models.Model): return True def action_cancel(self): - if not self.env.user.is_logistic_approver and (self.env.context.get('active_model') == 'stock.picking' or self.env.context.get('active_model') == 'stock.picking.type'): + if not self.env.user.is_logistic_approver and ( + self.env.context.get('active_model') == 'stock.picking' or self.env.context.get( + 'active_model') == 'stock.picking.type'): if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") - - if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group('indoteknik_custom.group_role_logistic') and self.picking_type_code == 'outgoing': + + if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group( + 'indoteknik_custom.group_role_logistic') and self.picking_type_code == 'outgoing': raise UserError("Button ini hanya untuk Logistik") res = super(StockPicking, self).action_cancel() @@ -1351,7 +1394,7 @@ class StockPicking(models.Model): def create(self, vals): self._use_faktur(vals) records = super(StockPicking, self).create(vals) - + # Panggil sync_sale_line setelah record dibuat # records.sync_sale_line(vals) return records @@ -1373,8 +1416,8 @@ class StockPicking(models.Model): def write(self, vals): if 'linked_manual_bu_out' in vals: for record in self: - if (record.picking_type_code == 'internal' - and 'BU/PICK/' in record.name): + if (record.picking_type_code == 'internal' + and 'BU/PICK/' in record.name): # Jika menghapus referensi (nilai di-set False/None) if record.linked_manual_bu_out and not vals['linked_manual_bu_out']: record.linked_manual_bu_out.state_packing = 'not_packing' @@ -1399,7 +1442,8 @@ class StockPicking(models.Model): if name_to_modify.startswith('BU/INT'): new_name = name_to_modify.replace('BU/INT', 'BU/IN', 1) # Periksa apakah nama sudah ada - if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', picking.company_id.id)]) > 0: + if self.env['stock.picking'].search_count( + [('name', '=', new_name), ('company_id', '=', picking.company_id.id)]) > 0: new_name = f"{new_name}-DUP" vals['name'] = new_name return super(StockPicking, self).write(vals) @@ -1443,7 +1487,7 @@ class StockPicking(models.Model): def get_manifests(self): if self.waybill_id and len(self.waybill_id.manifest_ids) > 0: return [self.create_manifest_data(x.description, x.datetime) for x in self.waybill_id.manifest_ids] - + status_mapping = { 'pickup': { 'arrival': 'Sudah diambil', @@ -1468,7 +1512,7 @@ class StockPicking(models.Model): if not status: return manifest_datas - + if arrival_date or self.sj_return_date: manifest_datas.append(self.create_manifest_data(status['arrival'], arrival_date)) if departure_date: @@ -1479,14 +1523,14 @@ class StockPicking(models.Model): def get_tracking_detail(self): self.ensure_one() - + order = self.env['sale.order'].search([('name', '=', self.sale_id.name)], limit=1) response = { 'delivery_order': { 'name': self.name, 'carrier': self.carrier_id.name or '', - 'service' : order.delivery_service_type or '', + 'service': order.delivery_service_type or '', 'receiver_name': '', 'receiver_city': '' }, @@ -1498,74 +1542,76 @@ class StockPicking(models.Model): 'is_biteship': True if self.biteship_id else False, 'manifests': self.get_manifests() } - - if self.biteship_id : + + if self.biteship_id: histori = self.get_manifest_biteship() eta_start = order.date_order + timedelta(days=order.estimated_arrival_days_start) eta_end = order.date_order + timedelta(days=order.estimated_arrival_days) formatted_eta = f"{eta_start.strftime('%d %b')} - {eta_end.strftime('%d %b %Y')}" response['eta'] = formatted_eta - response['manifests'] = histori.get("manifests", []) - response['delivered'] = histori.get("delivered", False) or self.sj_return_date != False or self.driver_arrival_date != False + response['manifests'] = histori.get("manifests", []) + response['delivered'] = histori.get("delivered", + False) or self.sj_return_date != False or self.driver_arrival_date != False response['status'] = self._map_status_biteship(histori.get("delivered")) - + return response if not self.waybill_id or len(self.waybill_id.manifest_ids) == 0: response['delivered'] = self.sj_return_date != False or self.driver_arrival_date != False return response - + response['delivery_order']['receiver_name'] = self.waybill_id.receiver_name response['delivery_order']['receiver_city'] = self.waybill_id.receiver_city response['delivery_status'] = self.waybill_id._get_history('delivery_status') response['delivered'] = self.waybill_id.delivered return response - + def get_manifest_biteship(self): api_key = _biteship_api_key headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } - - + manifests = [] - + try: - # Kirim request ke Biteship - response = requests.get(_biteship_url+'/trackings/'+self.biteship_tracking_id, headers=headers, json=manifests) + # Kirim request ke Biteship + response = requests.get(_biteship_url + '/trackings/' + self.biteship_tracking_id, headers=headers, + json=manifests) result = response.json() description = { - 'confirmed' : 'Indoteknik telah melakukan permintaan pick-up', - 'allocated' : 'Kurir akan melakukan pick-up pesanan', - 'picking_up' : 'Kurir sedang dalam perjalanan menuju lokasi pick-up', - 'picked' : 'Pesanan sudah di pick-up kurir '+result.get("courier", {}).get("name", ""), - 'on_hold' : 'Pesanan ditahan sementara karena masalah pengiriman', - 'dropping_off' : 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', - 'delivered' : 'Pesanan telah sampai dan diterima oleh '+result.get("destination", {}).get("contact_name", "") + 'confirmed': 'Indoteknik telah melakukan permintaan pick-up', + 'allocated': 'Kurir akan melakukan pick-up pesanan', + 'picking_up': 'Kurir sedang dalam perjalanan menuju lokasi pick-up', + 'picked': 'Pesanan sudah di pick-up kurir ' + result.get("courier", {}).get("name", ""), + 'on_hold': 'Pesanan ditahan sementara karena masalah pengiriman', + 'dropping_off': 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli', + 'delivered': 'Pesanan telah sampai dan diterima oleh ' + result.get("destination", {}).get( + "contact_name", "") } - if(result.get('success') == True): + if (result.get('success') == True): history = result.get("history", []) status = result.get("status", "") - + for entry in reversed(history): manifests.append({ "status": re.sub(r'[^a-zA-Z0-9\s]', ' ', entry["status"]).lower().capitalize(), "datetime": self._convert_to_local_time(entry["updated_at"]), - "description": description[entry["status"]], + "description": description[entry["status"]], }) - + return { "manifests": manifests, "delivered": status } return manifests - except Exception as e : + except Exception as e: _logger.error(f"Error fetching Biteship order for picking {self.id}: {str(e)}") - return { 'error': str(e) } - + return {'error': str(e)} + def _convert_to_local_time(self, iso_date): try: dt_with_tz = waktu.fromisoformat(iso_date) @@ -1577,7 +1623,7 @@ class StockPicking(models.Model): return local_dt.strftime("%Y-%m-%d %H:%M:%S") except Exception as e: return str(e) - + def _map_status_biteship(self, status): status_mapping = { "confirmed": "pending", @@ -1591,7 +1637,7 @@ class StockPicking(models.Model): "delivered": "completed" } return status_mapping.get(status, "Hubungi Admin") - + def generate_eta_delivery(self): current_date = datetime.datetime.now() prepare_days = 3 @@ -1605,7 +1651,7 @@ class StockPicking(models.Model): fastest_eta = start_date + ead_datetime if not self.driver_departure_date and fastest_eta < current_date: fastest_eta = current_date + ead_datetime - + longest_days = 3 longest_eta = fastest_eta + datetime.timedelta(days=longest_days) @@ -1614,9 +1660,10 @@ class StockPicking(models.Model): formatted_fastest_eta = fastest_eta.strftime(format_time_fastest) formatted_longest_eta = longest_eta.strftime(format_time) - + return f'{formatted_fastest_eta} - {formatted_longest_eta}' - + + class CheckProduct(models.Model): _name = 'check.product' _description = 'Check Product' @@ -1639,7 +1686,7 @@ class CheckProduct(models.Model): def _onchange_code_product(self): if not self.code_product: return - + # Cari product berdasarkan default_code, barcode, atau barcode_box product = self.env['product.product'].search([ '|', @@ -1648,10 +1695,10 @@ class CheckProduct(models.Model): ('barcode', '=', self.code_product), ('barcode_box', '=', self.code_product) ], limit=1) - + if not product: raise UserError("Product tidak ditemukan") - + # Jika scan barcode_box, set quantity sesuai qty_pcs_box if product.barcode_box == self.code_product: self.product_id = product.id @@ -1660,7 +1707,7 @@ class CheckProduct(models.Model): # return { # 'warning': { # 'title': 'Info',8994175025871 - + # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}' # } # } @@ -1673,29 +1720,29 @@ class CheckProduct(models.Model): def unlink(self): # Get all affected pickings before deletion pickings = self.mapped('picking_id') - + # Store product_ids that will be deleted deleted_product_ids = self.mapped('product_id') - + # Perform the deletion result = super(CheckProduct, self).unlink() - + # After deletion, update moves for affected pickings for picking in pickings: # For products that were completely removed (no remaining check.product lines) remaining_product_ids = picking.check_product_lines.mapped('product_id') removed_product_ids = deleted_product_ids - remaining_product_ids - + # Set quantity_done to 0 for moves of completely removed products moves_to_reset = picking.move_ids_without_package.filtered( lambda move: move.product_id in removed_product_ids ) for move in moves_to_reset: move.quantity_done = 0.0 - + # Also sync remaining products in case their totals changed self._sync_check_product_to_moves(picking) - + return result @api.depends('quantity') @@ -1711,7 +1758,6 @@ class CheckProduct(models.Model): else: record.status = 'Done' - def create(self, vals): # Create the record record = super(CheckProduct, self).create(vals) @@ -1736,7 +1782,8 @@ class CheckProduct(models.Model): for product_id in picking.check_product_lines.mapped('product_id'): # Totalkan quantity dari semua baris check.product untuk product_id ini total_quantity = sum( - line.quantity for line in picking.check_product_lines.filtered(lambda line: line.product_id == product_id) + line.quantity for line in + picking.check_product_lines.filtered(lambda line: line.product_id == product_id) ) # Update quantity_done di move yang relevan moves = picking.move_ids_without_package.filtered(lambda move: move.product_id == product_id) @@ -1789,8 +1836,8 @@ class CheckProduct(models.Model): if not moves: raise UserError(( - "The product '%s' tidak ada di operations. " - ) % record.product_id.display_name) + "The product '%s' tidak ada di operations. " + ) % record.product_id.display_name) total_qty_in_moves = sum(moves.mapped('product_uom_qty')) @@ -1808,18 +1855,19 @@ class CheckProduct(models.Model): if total_quantity > total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand." - ) % (record.product_id.display_name)) + "Quantity Product '%s' sudah melebihi quantity demand." + ) % (record.product_id.display_name)) else: # Check if the quantity exceeds the allowed total if record.quantity == total_qty_in_moves: raise UserError(( - "Quantity Product '%s' sudah melebihi quantity demand." - ) % (record.product_id.display_name)) + "Quantity Product '%s' sudah melebihi quantity demand." + ) % (record.product_id.display_name)) # Set the quantity to the entered value record.quantity = record.quantity + class BarcodeProduct(models.Model): _name = 'barcode.product' _description = 'Barcode Product' @@ -1841,7 +1889,7 @@ class BarcodeProduct(models.Model): if barcode_product: raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name)) - + barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)]) if barcode_box: @@ -1855,7 +1903,8 @@ class BarcodeProduct(models.Model): record.product_id.barcode = record.barcode else: raise UserError('Barcode sudah terisi') - + + class CheckKoli(models.Model): _name = 'check.koli' _description = 'Check Koli' @@ -1885,7 +1934,8 @@ class CheckKoli(models.Model): 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' _description = 'Scan Koli' @@ -1929,18 +1979,18 @@ class ScanKoli(models.Model): def _onchange_koli_compare_with_konfirm_koli(self): if not self.koli_id: return - + if not self.picking_id.konfirm_koli_lines: raise UserError(_('Mapping Koli Harus Diisi!')) - + koli_picking = self.koli_id.picking_id._origin - + konfirm_pick_ids = [ - line.pick_id._origin - for line in self.picking_id.konfirm_koli_lines + line.pick_id._origin + for line in self.picking_id.konfirm_koli_lines if line.pick_id ] - + if koli_picking not in konfirm_pick_ids: raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!')) @@ -1951,7 +2001,7 @@ class ScanKoli(models.Model): existing_koli = self.search([ ('picking_id', '=', record.picking_id.id), ('koli_id', '=', record.koli_id.id), - ('id', '!=', record.id) + ('id', '!=', record.id) ]) if existing_koli: raise ValidationError(f"⚠️ Koli '{record.koli_id.display_name}' sudah discan untuk picking ini!") @@ -1974,22 +2024,23 @@ class ScanKoli(models.Model): 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.")) - + 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 super(ScanKoli, self).unlink() - @api.onchange('koli_id','scan_koli_progress') + @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: + 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 @@ -1998,7 +2049,7 @@ class ScanKoli(models.Model): if not scan.picking_id: scan.scan_koli_progress = "0/0" continue - + try: all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id') if all_scans: @@ -2010,16 +2061,17 @@ class ScanKoli(models.Model): except Exception: # Fallback in case of any error scan.scan_koli_progress = "0/0" + @api.constrains('picking_id', 'picking_id.total_so_koli') def _check_koli_validation(self): 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 - + 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)]) @@ -2033,22 +2085,22 @@ class ScanKoli(models.Model): 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: return - + koli_count_by_picking = defaultdict(int) for scan in self: 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: 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)]) @@ -2056,21 +2108,23 @@ 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 += pick_move.qty_done + corresponding_out_move.qty_done += pick_move.qty_done def _reset_qty_done_if_no_scan(self, picking_id): - product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)]) + 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.") - 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 - # return remaining_scans class KonfirmKoli(models.Model): _name = 'konfirm.koli' @@ -2099,7 +2153,8 @@ class KonfirmKoli(models.Model): 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' @@ -2112,5 +2167,3 @@ class WarningModalWizard(models.TransientModel): 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 4fc562965d80b3315e58568c42075edf154256dc Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 11:31:32 +0700 Subject: refactor ask approval retur --- indoteknik_custom/models/stock_picking.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 1291737e..0b78706c 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -947,6 +947,8 @@ class StockPicking(models.Model): if self.env.user.is_accounting: pick.approval_return_status = 'approved' continue + else: + pick.approval_return_status = 'pengajuan1' action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard') -- cgit v1.2.3 From fcc0596b649ef53a6280530b60c89a2a0dc0ebec Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 May 2025 17:01:05 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 0b78706c..fadcc3c2 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1203,7 +1203,7 @@ class StockPicking(models.Model): if not invoice: continue - if not picking.so_lama and (not picking.date_doc_kirim or not invoice.invoice_date): + if not picking.so_lama and invoice and (not picking.date_doc_kirim or not invoice.invoice_date): raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!") picking_date = fields.Date.to_date(picking.date_doc_kirim) -- cgit v1.2.3 From 8c8cc93b60dfef60342ccd47b440f702bc18c754 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 7 May 2025 16:20:15 +0700 Subject: change trigger unlink note pj and add validation fill sj return date --- indoteknik_custom/models/stock_picking.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index f431d817..d032f99f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -276,6 +276,15 @@ class StockPicking(models.Model): last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') + @api.constrains('sj_return_date') + def _check_sj_return_date(self): + for record in self: + if not record.driver_arrival_date: + if record.sj_return_date: + raise ValidationError( + _("Anda tidak dapat mengubah Tanggal Pengembalian setelah Tanggal Pengiriman!") + ) + def _check_date_doc_kirim_modification(self): for record in self: if record.last_update_date_doc_kirim and not self.env.context.get('from_button_approve'): -- cgit v1.2.3 From 6b3401c7542e26830d60667fa0dbd78c25cc7be9 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 8 May 2025 09:39:29 +0700 Subject: push --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index d032f99f..ccccbcdc 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1248,7 +1248,7 @@ class StockPicking(models.Model): continue invoice = self.env['account.move'].search( - [('sale_id', '=', picking.sale_id.id), ('state', 'not in', ['draft', 'cancel'])], limit=1) + [('sale_id', '=', picking.sale_id.id), ('state', 'not in', ['draft', 'cancel']), ('move_type', '=', 'out_invoice')], limit=1) if not invoice: continue -- cgit v1.2.3 From 81a60ec161deb7eba3072172744276d6e5457f64 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 9 May 2025 15:27:38 +0700 Subject: integration api kgx --- indoteknik_custom/models/stock_picking.py | 74 +++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index ccccbcdc..dd2365ec 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -255,6 +255,13 @@ 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") + # KGX Section + kgx_pod_photo_url = fields.Char('KGX Photo URL') + kgx_pod_photo = fields.Html('KGX Photo', compute='_compute_kgx_image_html') + kgx_pod_signature = fields.Char('KGX Signature URL') + kgx_pod_receive_time = fields.Datetime('KGX Ata Date') + kgx_pod_receiver = fields.Char('KGX Receiver') + 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) @@ -276,6 +283,66 @@ class StockPicking(models.Model): last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim') update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD') + def _get_kgx_awb_number(self): + """Menggabungkan name dan origin untuk membuat AWB Number""" + self.ensure_one() + if not self.name or not self.origin: + return False + return f"{self.name} {self.origin}" + + def _download_pod_photo(self, url): + """Mengunduh foto POD dari URL""" + try: + response = requests.get(url, timeout=10) + response.raise_for_status() + return base64.b64encode(response.content) + except Exception as e: + raise UserError(f"Gagal mengunduh foto POD: {str(e)}") + + def _parse_datetime(self, dt_str): + """Parse datetime string dari format KGX""" + try: + from datetime import datetime + # Hilangkan timezone jika ada masalah parsing + if '+' in dt_str: + dt_str = dt_str.split('+')[0] + return datetime.strptime(dt_str, '%Y-%m-%dT%H:%M:%S') + except ValueError: + return False + + def action_get_kgx_pod(self): + self.ensure_one() + + awb_number = self._get_kgx_awb_number() + if not awb_number: + raise UserError("Nomor AWB tidak dapat dibuat, pastikan picking memiliki name dan origin") + + url = "https://kgx.co.id/get_detail_awb" + headers = {'Content-Type': 'application/json'} + payload = {"params" : {'awb_number': awb_number}} + + try: + response = requests.post(url, headers=headers, data=json.dumps(payload)) + response.raise_for_status() + data = response.json() + + if data.get('result', {}).get('data', []): + pod_data = data['result']['data'][0].get('connote_pod', {}) + photo_url = pod_data.get('photo') + + self.kgx_pod_photo_url = photo_url + self.kgx_pod_signature = pod_data.get('signature') + self.kgx_pod_receiver = pod_data.get('receiver') + self.kgx_pod_receive_time = self._parse_datetime(pod_data.get('timeReceive')) + self.driver_arrival_date = self._parse_datetime(pod_data.get('timeReceive')) + + return data + else: + raise UserError(f"Tidak ditemukan data untuk AWB: {awb_number}") + + except requests.exceptions.RequestException as e: + raise UserError(f"Gagal mengambil data POD: {str(e)}") + @api.constrains('sj_return_date') def _check_sj_return_date(self): for record in self: @@ -454,6 +521,13 @@ class StockPicking(models.Model): else: record.lalamove_image_html = "No image available." + def _compute_kgx_image_html(self): + for record in self: + if record.kgx_pod_photo_url: + record.kgx_pod_photo = f'' + else: + record.kgx_pod_photo = "No image available." + def action_fetch_lalamove_order(self): pickings = self.env['stock.picking'].search([ ('picking_type_code', '=', 'outgoing'), -- cgit v1.2.3 From f134f03f0e9998001a6fec44502a1e5a0a6b821b Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 13 May 2025 11:13:36 +0700 Subject: fix bug return, and change view project --- indoteknik_custom/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/stock_picking.py') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index dd2365ec..6a6fe352 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -87,7 +87,7 @@ class StockPicking(models.Model): ) sj_documentation = fields.Binary(string="Dokumentasi Surat Jalan", ) paket_documentation = fields.Binary(string="Dokumentasi Paket", ) - sj_return_date = fields.Datetime(string="SJ Return Date", ) + sj_return_date = fields.Datetime(string="SJ Return Date", copy=False) responsible = fields.Many2one('res.users', string='Responsible', tracking=True) approval_status = fields.Selection([ -- cgit v1.2.3