summaryrefslogtreecommitdiff
path: root/indoteknik_custom/models/stock_picking.py
diff options
context:
space:
mode:
Diffstat (limited to 'indoteknik_custom/models/stock_picking.py')
-rw-r--r--indoteknik_custom/models/stock_picking.py283
1 files changed, 242 insertions, 41 deletions
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index aa616e62..36129f00 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,12 +100,11 @@ 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'),
+ ('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 Logistix (Text)", help="Catatan untuk pengiriman")
state_approve_md = fields.Selection([
('waiting', 'Waiting For Approve by MD'),
('pending', 'Pending (perlu koordinasi dengan MD)'),
@@ -154,8 +154,33 @@ 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')
-
+ 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:
+ # 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:
@@ -220,21 +245,68 @@ 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('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'
+ 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.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!")
+ )
+
+ @api.constrains('date_doc_kirim')
+ def _constrains_date_doc_kirim(self):
+ for rec in self:
+ 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')
+
+ 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)
+
+ 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 = datetime.datetime.utcnow()
+
@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:
+ 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')
@@ -930,6 +1002,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')
@@ -941,7 +1015,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
@@ -963,6 +1036,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."))
@@ -1021,9 +1097,7 @@ 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.driver_departure_date = datetime.datetime.utcnow()
self.state_reserve = 'done'
self.final_seq = 0
self.set_picking_code_out()
@@ -1056,6 +1130,28 @@ 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)], limit=1)
+
+ if not invoice:
+ continue
+
+ if 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:
+ 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
@@ -1214,6 +1310,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:
@@ -1484,9 +1591,73 @@ 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')
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 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
+ 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):
@@ -1586,7 +1757,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:
@@ -1594,7 +1765,7 @@ 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:
raise UserError((
@@ -1602,7 +1773,7 @@ class CheckProduct(models.Model):
) % (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))
@@ -1626,9 +1797,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:
@@ -1683,15 +1866,25 @@ class ScanKoli(models.Model):
string="Progress Scan Koli",
compute="_compute_scan_koli_progress"
)
+ code_koli = fields.Char(string='Code Koli')
- 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('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:
+ # 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):
@@ -1763,13 +1956,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: