diff options
| author | Azka Nathan <darizkyfaz@gmail.com> | 2025-04-11 13:19:29 +0700 |
|---|---|---|
| committer | Azka Nathan <darizkyfaz@gmail.com> | 2025-04-11 13:19:29 +0700 |
| commit | 88441307195715206318d19225b044201978fd6f (patch) | |
| tree | 857025ab51aad4c6420d13cf26732861d97ddc1a | |
| parent | 10e3915bccc758ffc2f6e0e1d2e19f590605339e (diff) | |
| parent | 00c69ce93bdb0071cd563be855857d2137115868 (diff) | |
Merge branch 'dev/wms' into odoo-backup
# Conflicts:
# indoteknik_custom/models/stock_picking.py
| -rwxr-xr-x | indoteknik_custom/models/__init__.py | 2 | ||||
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 1 | ||||
| -rw-r--r-- | indoteknik_custom/models/sales_order_koli.py | 26 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_backorder_confirmation.py | 33 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_immediate_transfer.py | 8 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_picking.py | 403 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_picking_return.py | 13 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 7 | ||||
| -rwxr-xr-x | indoteknik_custom/views/sale_order.xml | 17 | ||||
| -rw-r--r-- | indoteknik_custom/views/stock_picking.xml | 86 |
10 files changed, 573 insertions, 23 deletions
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 37a49332..935a0aa0 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -145,5 +145,7 @@ from . import coretax_fatur from . import public_holiday from . import ir_actions_report from . import barcoding_product +from . import sales_order_koli +from . import stock_backorder_confirmation from . import account_payment_register from . import stock_inventory diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8d9af692..c83ffd61 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -67,6 +67,7 @@ class ShippingOption(models.Model): class SaleOrder(models.Model): _inherit = "sale.order" + koli_lines = fields.One2many('sales.order.koli', 'sale_order_id', string='Sales Order Koli', auto_join=True) fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2') fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment') reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines') diff --git a/indoteknik_custom/models/sales_order_koli.py b/indoteknik_custom/models/sales_order_koli.py new file mode 100644 index 00000000..c782a40e --- /dev/null +++ b/indoteknik_custom/models/sales_order_koli.py @@ -0,0 +1,26 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import AccessError, UserError, ValidationError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +import logging + +_logger = logging.getLogger(__name__) + + +class SalesOrderKoli(models.Model): + _name = 'sales.order.koli' + _description = 'Sales Order Koli' + _order = 'sale_order_id, id' + _rec_name = 'koli_id' + + sale_order_id = fields.Many2one( + 'sale.order', + string='Sale Order Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + koli_id = fields.Many2one('check.koli', string='Koli') + picking_id = fields.Many2one('stock.picking', string='Picking') + state = fields.Selection([('not_delivered', 'Not Delivered'), ('delivered', 'Delivered')], string='Status', default='not_delivered') + diff --git a/indoteknik_custom/models/stock_backorder_confirmation.py b/indoteknik_custom/models/stock_backorder_confirmation.py new file mode 100644 index 00000000..d8a41f54 --- /dev/null +++ b/indoteknik_custom/models/stock_backorder_confirmation.py @@ -0,0 +1,33 @@ +from odoo import models, fields, api +from odoo.tools.float_utils import float_compare + +class StockBackorderConfirmation(models.TransientModel): + _inherit = 'stock.backorder.confirmation' + + def process(self): + pickings_to_do = self.env['stock.picking'] + pickings_not_to_do = self.env['stock.picking'] + for line in self.backorder_confirmation_line_ids: + line.picking_id.send_mail_bills() + # line.picking_id.send_koli_to_so() + if line.to_backorder is True: + pickings_to_do |= line.picking_id + else: + pickings_not_to_do |= line.picking_id + + for pick_id in pickings_not_to_do: + moves_to_log = {} + for move in pick_id.move_lines: + if float_compare(move.product_uom_qty, + move.quantity_done, + precision_rounding=move.product_uom.rounding) > 0: + moves_to_log[move] = (move.quantity_done, move.product_uom_qty) + pick_id._log_less_quantities_than_expected(moves_to_log) + + pickings_to_validate = self.env.context.get('button_validate_picking_ids') + if pickings_to_validate: + pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate).with_context(skip_backorder=True) + if pickings_not_to_do: + pickings_to_validate = pickings_to_validate.with_context(picking_ids_not_to_backorder=pickings_not_to_do.ids) + return pickings_to_validate.button_validate() + return True diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index 21210619..c2a293f9 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -5,17 +5,20 @@ class StockImmediateTransfer(models.TransientModel): _inherit = 'stock.immediate.transfer' def process(self): - """Override process method to add send_mail_bills logic.""" pickings_to_do = self.env['stock.picking'] pickings_not_to_do = self.env['stock.picking'] for line in self.immediate_transfer_line_ids: + line.picking_id.send_mail_bills() + line.picking_id.send_koli_to_so() if line.to_immediate is True: pickings_to_do |= line.picking_id else: pickings_not_to_do |= line.picking_id for picking in pickings_to_do: + # picking.send_mail_bills() + # picking.send_koli_to_so() # If still in draft => confirm and assign if picking.state == 'draft': picking.action_confirm() @@ -23,6 +26,7 @@ class StockImmediateTransfer(models.TransientModel): picking.action_assign() if picking.state != 'assigned': raise UserError(_("Could not reserve all requested products. Please use the 'Mark as Todo' button to handle the reservation manually.")) + for move in picking.move_lines.filtered(lambda m: m.state not in ['done', 'cancel']): for move_line in move.move_line_ids: move_line.qty_done = move_line.product_uom_qty @@ -32,4 +36,6 @@ class StockImmediateTransfer(models.TransientModel): pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate) pickings_to_validate = pickings_to_validate - pickings_not_to_do return pickings_to_validate.with_context(skip_immediate=True).button_validate() + return True + diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a1b28385..b1243e95 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1,6 +1,8 @@ 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 datetime import timedelta, datetime as waktu from itertools import groupby import pytz, requests, json, requests @@ -24,6 +26,9 @@ _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) 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) @@ -142,6 +147,9 @@ class StockPicking(models.Model): # 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" + quantity_koli = fields.Float(string="Quantity Koli", copy=False) + + @api.model def _compute_dokumen_tanda_terima(self): @@ -186,6 +194,11 @@ 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_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) + total_so_koli = fields.Integer(compute='_compute_total_so_koli', string="Total SO Koli") + # Biteship Section biteship_id = fields.Char(string="Biteship Respon ID") biteship_tracking_id = fields.Char(string="Biteship Trackcking ID") @@ -195,6 +208,56 @@ 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): + 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')]) + 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): + 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.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)] + 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 schduled_update_sequance(self): query = "SELECT update_sequance_stock_picking();" @@ -448,7 +511,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 }) @@ -932,6 +995,22 @@ 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.")) + + # 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)) + 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") @@ -979,14 +1058,77 @@ class StockPicking(models.Model): self.validation_minus_onhand_quantity() self.responsible = self.env.user.id + # 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' self.final_seq = 0 + 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', + } self.send_mail_bills() 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: + 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: + 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: @@ -1067,22 +1209,12 @@ class StockPicking(models.Model): res = super(StockPicking, self).action_cancel() return res - @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 - return super(StockPicking, self).create(vals) + records = super(StockPicking, self).create(vals) + + return records def write(self, vals): self._use_faktur(vals) @@ -1480,4 +1612,245 @@ 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' + _rec_name = 'koli' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + 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' + _description = 'Scan Koli' + _order = 'picking_id, id' + _rec_name = 'koli_id' + + picking_id = fields.Many2one( + 'stock.picking', + string='Picking Reference', + required=True, + ondelete='cascade', + index=True, + copy=False, + ) + 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): + 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: + 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 + if line.pick_id + ] + + if koli_picking not in konfirm_pick_ids: + raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!')) + + @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) + ]) + 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')) + for scan in self: + koli = scan.koli_id.koli_id + if koli: + koli.reserved_id = False + + for picking_id in picking_ids: + remaining_scans = self.env['sales.order.koli'].search_count([ + ('koli_id.picking_id', '=', picking_id) + ]) + + delete_koli = len(self.filtered(lambda rec: rec.koli_id.picking_id.id == picking_id)) + + 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 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): + 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.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)]) + # 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): + 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: + 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)]) + + 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): + 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 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') + + @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' + + name = fields.Char(default="⚠️ Perhatian!") + message = fields.Text() + picking_id = fields.Many2one('stock.picking') + + def action_continue(self): + if self.picking_id: + return self.picking_id.with_context(skip_koli_check=True).button_validate() + return {'type': 'ir.actions.act_window_close'} + + diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index d4347235..a683d80e 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -24,4 +24,15 @@ class ReturnPicking(models.TransientModel): # if not stock_picking.approval_return_status == 'approved' and purchase.invoice_ids: # raise UserError('Harus Approval Accounting AP untuk melakukan Retur') - return res
\ No newline at end of file + return res + +class ReturnPickingLine(models.TransientModel): + _inherit = 'stock.return.picking.line' + + @api.onchange('quantity') + def _onchange_quantity(self): + for rec in self: + qty_done = rec.move_id.quantity_done + + if rec.quantity > qty_done: + raise UserError(f"Quantity yang Anda masukkan tidak boleh melebihi quantity done yaitu: {qty_done}")
\ No newline at end of file diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index ab377fd3..098fe49c 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -153,9 +153,16 @@ access_va_multi_approve,access.va.multi.approve,model_va_multi_approve,,1,1,1,1 access_va_multi_reject,access.va.multi.reject,model_va_multi_reject,,1,1,1,1 access_vendor_sla,access.vendor_sla,model_vendor_sla,,1,1,1,1 access_check_product,access.check.product,model_check_product,,1,1,1,1 +access_check_koli,access.check.koli,model_check_koli,,1,1,1,1 +access_scan_koli,access.scan.koli,model_scan_koli,,1,1,1,1 +access_konfirm_koli,access.konfirm.koli,model_konfirm_koli,,1,1,1,1 access_stock_immediate_transfer,access.stock.immediate.transfer,model_stock_immediate_transfer,,1,1,1,1 access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1 access_purchase_order_unlock_wizard,access.purchase.order.unlock.wizard,model_purchase_order_unlock_wizard,,1,1,1,1 +access_sales_order_koli,access.sales.order.koli,model_sales_order_koli,,1,1,1,1 +access_stock_backorder_confirmation,access.stock.backorder.confirmation,model_stock_backorder_confirmation,,1,1,1,1 +access_warning_modal_wizard,access.warning.modal.wizard,model_warning_modal_wizard,,1,1,1,1 + access_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pengajuan_tempo_line,,1,1,1,1 access_user_pengajuan_tempo,access.user.pengajuan.tempo,model_user_pengajuan_tempo,,1,1,1,1 access_reject_reason_wizard,reject.reason.wizard,model_reject_reason_wizard,,1,1,1,0 diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 74730e06..2c64181e 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -286,6 +286,9 @@ <page string="Reject Line" name="page_sale_order_reject_line"> <field name="reject_line" readonly="1"/> </page> + <page string="Koli" name="page_sales_order_koli_line"> + <field name="koli_lines" readonly="1"/> + </page> </page> </field> </record> @@ -424,6 +427,20 @@ </data> <data> + <record id="sales_order_koli_tree" model="ir.ui.view"> + <field name="name">sales.order.koli.tree</field> + <field name="model">sales.order.koli</field> + <field name="arch" type="xml"> + <tree editable="top" create="false" delete="false"> + <field name="koli_id" readonly="1"/> + <field name="picking_id" readonly="1"/> + <field name="state" readonly="1"/> + </tree> + </field> + </record> + </data> + + <data> </data> <record id="sales_order_fulfillment_v2_tree" model="ir.ui.view"> diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 72fdefa7..7b4ba2f8 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -88,6 +88,9 @@ <field name="count_line_detail"/> <field name="dokumen_tanda_terima"/> <field name="dokumen_pengiriman"/> + <field name="quantity_koli" attrs="{'invisible': [('location_dest_id', '!=', 60)], 'required': [('location_dest_id', '=', 60)]}"/> + <field name="total_koli_display" readonly="1" attrs="{'invisible': [('location_id', '!=', 60)]}"/> + <field name="linked_out_picking_id" readonly="1" attrs="{'invisible': [('location_id', '=', 60)]}"/> </field> <field name="weight_uom_name" position="after"> <group> @@ -162,7 +165,7 @@ </group> </group> </page> - <page string="Delivery" name="delivery_order"> + <page string="Delivery" name="delivery_order" attrs="{'invisible': [('location_dest_id', '=', 60)]}"> <group> <group> <field name="notee"/> @@ -210,25 +213,67 @@ </group> </group> </page> - <page string="Check Product" name="check_product"> + <page string="Check Product" name="check_product" attrs="{'invisible': [('picking_type_code', '=', 'outgoing')]}"> <field name="check_product_lines"/> </page> <page string="Barcode Product" name="barcode_product" attrs="{'invisible': [('picking_type_code', '!=', 'incoming')]}"> <field name="barcode_product_lines"/> </page> + <page string="Check Koli" name="check_koli" attrs="{'invisible': [('location_dest_id', '!=', 60)]}"> + <field name="check_koli_lines"/> + </page> + <page string="Mapping Koli" name="konfirm_koli" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}"> + <field name="konfirm_koli_lines"/> + </page> + <page string="Konfirm Koli" name="scan_koli" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}"> + <field name="scan_koli_lines"/> + </page> </page> </field> </record> + <record id="scan_koli_tree" model="ir.ui.view"> + <field name="name">scan.koli.tree</field> + <field name="model">scan.koli</field> + <field name="arch" type="xml"> + <tree editable="bottom"> + <field name="koli_id" options="{'no_create': True}" required="1" domain="[('state', '=', 'not_delivered')]"/> + <field name="scan_koli_progress"/> + </tree> + </field> + </record> + + <record id="konfirm_koli_tree" model="ir.ui.view"> + <field name="name">konfirm.koli.tree</field> + <field name="model">konfirm.koli</field> + <field name="arch" type="xml"> + <tree editable="bottom"> + <field name="pick_id" options="{'no_create': True}" required="1" domain="[('picking_type_code', '=', 'internal'), ('group_id', '=', parent.group_id)]"/> + </tree> + </field> + </record> + + <record id="check_koli_tree" model="ir.ui.view"> + <field name="name">check.koli.tree</field> + <field name="model">check.koli</field> + <field name="arch" type="xml"> + <tree editable="bottom"> + <field name="koli"/> + <field name="reserved_id"/> + <field name="check_koli_progress"/> + </tree> + </field> + </record> + <record id="check_product_tree" model="ir.ui.view"> <field name="name">check.product.tree</field> <field name="model">check.product</field> <field name="arch" type="xml"> <tree editable="bottom" decoration-warning="status == 'Pending'" decoration-success="status == 'Done'"> - <field name="product_id"/> - <field name="quantity"/> - <field name="status"/> + <field name="product_id" required="1" options="{'no_create': True}"/> + <field name="quantity" readonly="1"/> + <field name="status" readonly="1"/> </tree> </field> </record> @@ -265,6 +310,35 @@ <field name="purchase_representative_id"/> </field> </field> - </record> + </record> + + <record id="view_warning_modal_wizard_form" model="ir.ui.view"> + <field name="name">warning.modal.wizard.form</field> + <field name="model">warning.modal.wizard</field> + <field name="arch" type="xml"> + <form string="Peringatan Koli Belum Diperiksa"> + <sheet> + <div class="oe_title"> + <h2><span>⚠️ Perhatian!</span></h2> + </div> + <group> + <field name="message" readonly="1" nolabel="1" widget="text"/> + </group> + </sheet> + <footer> + <button name="action_continue" type="object" string="Lanjutkan" class="btn-primary"/> + <button string="Tutup" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> + + <record id="action_warning_modal_wizard" model="ir.actions.act_window"> + <field name="name">Peringatan Koli</field> + <field name="res_model">warning.modal.wizard</field> + <field name="view_mode">form</field> + <field name="view_id" ref="view_warning_modal_wizard_form"/> + <field name="target">new</field> + </record> </data> </odoo>
\ No newline at end of file |
