summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAzka Nathan <darizkyfaz@gmail.com>2025-10-22 13:47:21 +0700
committerAzka Nathan <darizkyfaz@gmail.com>2025-10-22 13:47:21 +0700
commit4a7dd26f06d2d59768724dd13350f8aa1f0719f5 (patch)
tree2ccccd600a6618989f64987381be6f01ea010b23
parent826f19a3f5de7747192fd8ed4ba0c247084ed03c (diff)
push locator
-rw-r--r--indoteknik_custom/models/stock_move.py47
-rw-r--r--indoteknik_custom/models/stock_picking.py70
-rw-r--r--indoteknik_custom/views/stock_picking.xml2
3 files changed, 92 insertions, 27 deletions
diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py
index a0c3ed95..e7ea8fd5 100644
--- a/indoteknik_custom/models/stock_move.py
+++ b/indoteknik_custom/models/stock_move.py
@@ -22,7 +22,6 @@ class StockMove(models.Model):
hold_outgoingg = fields.Boolean('Hold Outgoing', default=False)
product_image = fields.Binary(related="product_id.image_128", string="Product Image", readonly=True)
partial = fields.Boolean('Partial?', default=False)
-
# Ambil product uom dari SO line
@api.model
def create(self, vals):
@@ -247,6 +246,52 @@ class StockMoveLine(models.Model):
line_no = fields.Integer('No', default=0)
note = fields.Char('Note')
manufacture = fields.Many2one('x_manufactures', string="Brands", related="product_id.x_manufacture", store=True)
+ outstanding_qty = fields.Float(
+ string='Outstanding Qty',
+ compute='_compute_delivery_line_status',
+ store=True
+ )
+ delivery_status = fields.Selection([
+ ('none', 'No Movement'),
+ ('partial', 'Partial'),
+ ('partial_final', 'Partial Final'),
+ ('full', 'Full'),
+ ], string='Delivery Status', compute='_compute_delivery_line_status', store=True)
+
+ @api.depends('qty_done', 'product_uom_qty', 'picking_id.state')
+ def _compute_delivery_line_status(self):
+ for line in self:
+ line.outstanding_qty = 0.0
+ line.delivery_status = 'none'
+
+ if not line.picking_id or line.picking_id.picking_type_id.code != 'outgoing':
+ continue
+
+ total_qty = line.move_id.product_uom_qty or 0
+ done_qty = line.qty_done or 0
+
+ # Hitung sisa qty
+ line.outstanding_qty = max(total_qty - done_qty, 0)
+
+ if total_qty == 0:
+ continue
+
+ # 🟢 full
+ if done_qty >= total_qty:
+ line.delivery_status = 'full'
+
+ # 🟡 partial
+ elif 0 < done_qty < total_qty:
+ # cek apakah masih ada backorder untuk picking ini
+ backorder_exists = self.env['stock.picking'].search_count([
+ ('backorder_id', '=', line.picking_id.id),
+ ('state', 'not in', ('done', 'cancel'))
+ ])
+ if backorder_exists:
+ line.delivery_status = 'partial'
+ else:
+ # 🔴 partial_final
+ line.delivery_status = 'partial_final'
# Ambil uom dari stock move
@api.model
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 3491ec26..c5b112a3 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -2191,33 +2191,44 @@ class CheckProduct(models.Model):
self.quantity = 1
def unlink(self):
- # Get all affected pickings before deletion
+ """
+ Override unlink untuk:
+ 1. Simpan picking dan product_id yang terdampak sebelum delete.
+ 2. Hapus record check.product.
+ 3. Reset quantity_done untuk product yang udah gak ada di check_product_lines.
+ 4. Sync ulang product yang masih ada.
+ """
+ # Step 1: ambil data sebelum hapus
pickings = self.mapped('picking_id')
-
- # Store product_ids that will be deleted
deleted_product_ids = self.mapped('product_id')
- # Perform the deletion
+ # Step 2: hapus record
result = super(CheckProduct, self).unlink()
- # After deletion, update moves for affected pickings
+ # Step 3: update masing-masing picking
for picking in pickings:
- # For products that were completely removed (no remaining check.product lines)
+ # pastikan picking masih valid (kadang record udah kehapus bareng cascade)
+ if not picking.exists():
+ continue
+
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
+ # Reset quantity_done untuk produk yang udah gak ada
+ if removed_product_ids:
+ moves_to_reset = picking.move_line_ids_without_package.filtered(
+ lambda m: m.product_id in removed_product_ids
+ )
+ for move_line in moves_to_reset:
+ move_line.qty_done = 0.0
- # Also sync remaining products in case their totals changed
- self._sync_check_product_to_moves(picking)
+ # Step 4: sync ulang product yang masih ada
+ if remaining_product_ids:
+ self._sync_check_product_to_moves(picking)
return result
+
@api.depends('quantity')
def _compute_status(self):
for record in self:
@@ -2249,33 +2260,40 @@ class CheckProduct(models.Model):
def _sync_check_product_to_moves(self, picking):
"""
- Sinkronisasi quantity_done di move_line_ids_without_package
+ Sinkronisasi qty_done di move_line_ids_without_package
berdasarkan total quantity dari check_product_lines per product_id,
- dan distribusikan ke masing-masing move_line.
+ dan distribusikan ke move_line berdasarkan urutan rack_level ascending.
"""
for product_id in picking.check_product_lines.mapped('product_id'):
- total_quantity = sum(
+ # Hitung total quantity dari check_product_lines untuk produk ini
+ remaining_qty = sum(
line.quantity
for line in picking.check_product_lines.filtered(lambda l: l.product_id == product_id)
)
- move_lines = picking.move_line_ids_without_package.filtered(lambda m: m.product_id == product_id)
+ # Ambil move_line untuk product_id ini dan urutkan berdasarkan rack_level ASC
+ move_lines = picking.move_line_ids_without_package.filtered(
+ lambda m: m.product_id == product_id
+ )
+ move_lines = sorted(
+ move_lines,
+ key=lambda m: m.location_id.rack_level or 0 # kalau rack_level kosong, dianggap 0
+ )
- remaining_qty = total_quantity
for move_line in move_lines:
- # ambil qty yang idealnya diisi (biasanya product_uom_qty - quantity_done)
- needed = move_line.product_uom_qty - move_line.qty_done
- if needed <= 0:
+ if remaining_qty <= 0:
+ move_line.qty_done = 0.0
continue
- # kalau sisa qty cukup, isi penuh; kalau enggak, isi sebagian
+ needed = move_line.product_uom_qty
assigned_qty = min(needed, remaining_qty)
+
move_line.qty_done = assigned_qty
remaining_qty -= assigned_qty
- # kalau sisa udah 0, berhenti aja
- if remaining_qty <= 0:
- break
+ # (Opsional) Log biar bisa dilacak
+ # _logger.info(f"[SYNC] {picking.name} - {product_id.display_name}: Sisa {remaining_qty}")
+
def _consolidate_duplicate_lines(self):
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml
index 44ab6355..7668946c 100644
--- a/indoteknik_custom/views/stock_picking.xml
+++ b/indoteknik_custom/views/stock_picking.xml
@@ -394,6 +394,8 @@
decoration-danger="qty_done&gt;product_uom_qty and state!='done' and parent.picking_type_code != 'incoming'"
decoration-success="qty_done==product_uom_qty and state!='done' and not result_package_id">
<field name="note" placeholder="Add a note here"/>
+ <field name="outstanding_qty"/>
+ <field name="delivery_status" widget="badge" options="{'colors': {'full': 'success', 'partial': 'warning', 'partial_final': 'danger'}}"/>
</tree>
</field>
</record>