summaryrefslogtreecommitdiff
path: root/indoteknik_custom/models/tukar_guling.py
diff options
context:
space:
mode:
authorIndoteknik . <it@fixcomart.co.id>2025-08-08 15:11:35 +0700
committerIndoteknik . <it@fixcomart.co.id>2025-08-08 15:11:35 +0700
commitda1903796553ca0ee5195a7962521bc09b4b98a8 (patch)
tree0fe68309a748a74e121c9f9c51cb4b047e7cd217 /indoteknik_custom/models/tukar_guling.py
parent7ebcf1675e55dae39f769466e5bf5ef064646f27 (diff)
parent19067dfea850b289b47d70ab36628b796ae87265 (diff)
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into reminder-tempo-v2
Diffstat (limited to 'indoteknik_custom/models/tukar_guling.py')
-rw-r--r--indoteknik_custom/models/tukar_guling.py197
1 files changed, 146 insertions, 51 deletions
diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py
index 8de5b671..eadb164c 100644
--- a/indoteknik_custom/models/tukar_guling.py
+++ b/indoteknik_custom/models/tukar_guling.py
@@ -2,6 +2,7 @@ from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
import logging
from datetime import datetime
+from collections import defaultdict
_logger = logging.getLogger(__name__)
@@ -479,13 +480,22 @@ class TukarGuling(models.Model):
# raise UserError("BU ini sudah pernah diretur oleh dokumen lain.")
if self.operations.picking_type_id.id == 29:
- for line in self.line_ids:
- mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
- total_qty = sum(l.qty_return for l in mapping_lines)
- if total_qty != line.product_uom_qty:
- raise UserError(
- _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name)
-
+ # Cek apakah ada BU/PICK di origin
+ origin = self.operations.origin
+ has_bu_pick = self.env['stock.picking'].search_count([
+ ('origin', '=', origin),
+ ('picking_type_id', '=', 30),
+ ('state', '!=', 'cancel')
+ ]) > 0
+
+ if has_bu_pick:
+ for line in self.line_ids:
+ mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
+ total_qty = sum(l.qty_return for l in mapping_lines)
+ if total_qty != line.product_uom_qty:
+ raise UserError(
+ _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name
+ )
# self._check_invoice_on_revisi_so()
self._validate_product_lines()
@@ -531,7 +541,7 @@ class TukarGuling(models.Model):
])
if self.state == 'approved' and done_ort:
self.state = 'done'
- else:
+ elif self.operations.picking_type_id.id == 30 and self.return_type == 'revisi_so' and not has_bu_pick:
raise UserError("Tidak bisa menentukan jenis retur.")
def action_approve(self):
@@ -544,12 +554,22 @@ class TukarGuling(models.Model):
tipe = self.return_type
if self.operations.picking_type_id.id == 29:
- for line in self.line_ids:
- mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
- total_qty = sum(l.qty_return for l in mapping_lines)
- if total_qty != line.product_uom_qty:
- raise UserError(
- _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name)
+ # Cek apakah ada BU/PICK di origin
+ origin = self.operations.origin
+ has_bu_pick = self.env['stock.picking'].search_count([
+ ('origin', '=', origin),
+ ('picking_type_id', '=', 30),
+ ('state', '!=', 'cancel')
+ ]) > 0
+
+ if has_bu_pick:
+ for line in self.line_ids:
+ mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
+ total_qty = sum(l.qty_return for l in mapping_lines)
+ if total_qty != line.product_uom_qty:
+ raise UserError(
+ _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name
+ )
if operasi == 30 and self.operations.linked_manual_bu_out.state == 'done':
raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT sudah done")
@@ -617,6 +637,23 @@ class TukarGuling(models.Model):
def _create_pickings(self):
_logger.info("🛠 Starting _create_pickings()")
+
+ def _force_locations(picking, from_loc, to_loc):
+ picking.write({
+ 'location_id': from_loc,
+ 'location_dest_id': to_loc,
+ })
+ for move in picking.move_lines:
+ move.write({
+ 'location_id': from_loc,
+ 'location_dest_id': to_loc,
+ })
+ for move_line in move.move_line_ids:
+ move_line.write({
+ 'location_id': from_loc,
+ 'location_dest_id': to_loc,
+ })
+
for record in self:
if not record.operations:
raise UserError("BU/OUT dari field operations tidak ditemukan.")
@@ -639,36 +676,70 @@ class TukarGuling(models.Model):
### ======== SRT dari BU/OUT =========
srt_return_lines = []
- for prod in mapping_koli.mapped('product_id'):
- qty_total = sum(mk.qty_return for mk in mapping_koli.filtered(lambda m: m.product_id == prod))
- move = bu_out.move_lines.filtered(lambda m: m.product_id == prod)
- if not move:
- raise UserError(f"Move BU/OUT tidak ditemukan untuk produk {prod.display_name}")
- srt_return_lines.append((0, 0, {
- 'product_id': prod.id,
- 'quantity': qty_total,
- 'move_id': move.id,
- }))
- _logger.info(f"📟 SRT line: {prod.display_name} | qty={qty_total}")
+ if mapping_koli:
+ move_per_product = defaultdict(list)
+ for move in bu_out.move_lines:
+ move_per_product[move.product_id.id].append(move)
+
+ mapped_product_ids = set(mapping_koli.mapped('product_id').ids)
+
+ for product_id in mapped_product_ids:
+ qty_ret = sum(
+ line.qty_return for line in mapping_koli.filtered(lambda l: l.product_id.id == product_id))
+ if qty_ret <= 0:
+ continue
+
+ product_moves = move_per_product[product_id]
+ if not product_moves:
+ raise UserError(f"❌ Move BU/OUT tidak ditemukan untuk product ID {product_id}")
+
+ if len(product_moves) > 1:
+ _logger.warning(f"🟠 Detected duplicate moves for product {product_id}, picking {bu_out.name}")
+ chosen_move = product_moves[0]
+ else:
+ chosen_move = product_moves[0]
+
+ srt_return_lines.append((0, 0, {
+ 'product_id': product_id,
+ 'quantity': qty_ret,
+ 'move_id': chosen_move.id,
+ }))
+ _logger.info(f"📟 SRT line: {chosen_move.product_id.display_name} | qty={qty_ret}")
+
+ else:
+ # --- Fallback ke line_ids jika tidak ada mapping_koli ---
+ for line in record.line_ids:
+ move = bu_out.move_lines.filtered(lambda m: m.product_id == line.product_id)
+ if not move:
+ raise UserError(f"❌ Move BU/OUT tidak ditemukan untuk produk {line.product_id.display_name}")
+ srt_return_lines.append((0, 0, {
+ 'product_id': line.product_id.id,
+ 'quantity': line.product_uom_qty,
+ 'move_id': move.id,
+ }))
+ _logger.info(f"📟 SRT line (fallback): {line.product_id.display_name} | qty={line.product_uom_qty}")
srt_picking = None
if srt_return_lines:
+ # Tentukan tujuan lokasi berdasarkan ada/tidaknya mapping_koli
+ dest_location_id = BU_OUTPUT_LOCATION_ID if mapping_koli else BU_STOCK_LOCATION_ID
+
srt_wizard = self.env['stock.return.picking'].with_context({
'active_id': bu_out.id,
'default_location_id': PARTNER_LOCATION_ID,
- 'default_location_dest_id': BU_OUTPUT_LOCATION_ID,
+ 'default_location_dest_id': dest_location_id,
'from_ui': False,
}).create({
'picking_id': bu_out.id,
'location_id': PARTNER_LOCATION_ID,
- 'original_location_id': BU_OUTPUT_LOCATION_ID,
'product_return_moves': srt_return_lines
})
+
srt_vals = srt_wizard.create_returns()
srt_picking = self.env['stock.picking'].browse(srt_vals['res_id'])
+ _force_locations(srt_picking, PARTNER_LOCATION_ID, dest_location_id)
+
srt_picking.write({
- 'location_id': PARTNER_LOCATION_ID,
- 'location_dest_id': BU_OUTPUT_LOCATION_ID,
'group_id': bu_out.group_id.id,
'tukar_guling_id': record.id,
'sale_order': record.origin
@@ -681,40 +752,66 @@ class TukarGuling(models.Model):
### ======== ORT dari BU/PICK =========
ort_pickings = []
is_retur_from_bu_pick = record.operations.picking_type_id.id == 30
- picks_to_return = [record.operations] if is_retur_from_bu_pick else mapping_koli.mapped(
- 'pick_id') or line.product_uom_qty
+ picks_to_return = [record.operations] if is_retur_from_bu_pick else mapping_koli.mapped('pick_id')
for pick in picks_to_return:
ort_return_lines = []
+
if is_retur_from_bu_pick:
- # Ambil dari tukar.guling.line
+ # Build map produk -> move list
+ move_map = defaultdict(list)
+ for move in pick.move_lines:
+ move_map[move.product_id.id].append(move)
+
for line in record.line_ids:
- move = pick.move_lines.filtered(lambda m: m.product_id == line.product_id)
- if not move:
+ moves = move_map.get(line.product_id.id)
+ if not moves:
raise UserError(
f"Move tidak ditemukan di BU/PICK {pick.name} untuk {line.product_id.display_name}")
+
+ chosen_move = moves[0]
+ if len(moves) > 1:
+ _logger.warning(
+ f"🟠 Duplicate move detected for {line.product_id.display_name} in {pick.name}. Using the first move only.")
+ pick.message_post(
+ body=f"🟠 Duplicate move ditemukan untuk produk <b>{line.product_id.display_name}</b>. Hanya 1 move yang dipakai.")
+
ort_return_lines.append((0, 0, {
'product_id': line.product_id.id,
'quantity': line.product_uom_qty,
- 'move_id': move.id,
+ 'move_id': chosen_move.id,
}))
_logger.info(
f"📟 ORT (BU/PICK langsung) | {pick.name} | {line.product_id.display_name} | qty={line.product_uom_qty}")
+
else:
- # Ambil dari mapping koli
+ # Mapping koli case
+ move_map = defaultdict(list)
+ for move in pick.move_lines:
+ move_map[move.product_id.id].append(move)
+
for mk in mapping_koli.filtered(lambda m: m.pick_id == pick):
- move = pick.move_lines.filtered(lambda m: m.product_id == mk.product_id)
- if not move:
+ moves = move_map.get(mk.product_id.id)
+ if not moves:
raise UserError(
f"Move tidak ditemukan di BU/PICK {pick.name} untuk {mk.product_id.display_name}")
+
+ chosen_move = moves[0]
+ if len(moves) > 1:
+ _logger.warning(
+ f"🟠 Duplicate move detected for {mk.product_id.display_name} in {pick.name}. Using the first move only.")
+ pick.message_post(
+ body=f"🟠 Duplicate move ditemukan untuk produk <b>{mk.product_id.display_name}</b>. Hanya 1 move yang dipakai.")
+
ort_return_lines.append((0, 0, {
'product_id': mk.product_id.id,
'quantity': mk.qty_return,
- 'move_id': move.id,
+ 'move_id': chosen_move.id,
}))
_logger.info(
f"📟 ORT (mapping koli) | {pick.name} | {mk.product_id.display_name} | qty={mk.qty_return}")
+ # Buat retur jika ada return line
if ort_return_lines:
ort_wizard = self.env['stock.return.picking'].with_context({
'active_id': pick.id,
@@ -724,27 +821,27 @@ class TukarGuling(models.Model):
}).create({
'picking_id': pick.id,
'location_id': BU_OUTPUT_LOCATION_ID,
- 'original_location_id': BU_STOCK_LOCATION_ID,
'product_return_moves': ort_return_lines
})
+
ort_vals = ort_wizard.create_returns()
ort_picking = self.env['stock.picking'].browse(ort_vals['res_id'])
+ _force_locations(ort_picking, BU_OUTPUT_LOCATION_ID, BU_STOCK_LOCATION_ID)
+
ort_picking.write({
- 'location_id': BU_OUTPUT_LOCATION_ID,
- 'location_dest_id': BU_STOCK_LOCATION_ID,
'group_id': bu_out.group_id.id,
'tukar_guling_id': record.id,
'sale_order': record.origin
})
+
created_returns.append(ort_picking)
ort_pickings.append(ort_picking)
_logger.info(f"✅ ORT created: {ort_picking.name}")
record.message_post(
body=f"📦 <b>{ort_picking.name}</b> created by <b>{self.env.user.name}</b> (state: <b>{ort_picking.state}</b>)")
- ### ======== Tukar Guling: BU/OUT dan BU/PICK baru ========
+ ### ======== BU/PICK & BU/OUT Baru dari SRT/ORT ========
if record.return_type == 'tukar_guling':
-
# BU/PICK Baru dari ORT
for ort_p in ort_pickings:
return_lines = []
@@ -770,19 +867,18 @@ class TukarGuling(models.Model):
}).create({
'picking_id': ort_p.id,
'location_id': BU_STOCK_LOCATION_ID,
- 'original_location_id': BU_OUTPUT_LOCATION_ID,
'product_return_moves': return_lines
})
bu_pick_vals = bu_pick_wizard.create_returns()
new_pick = self.env['stock.picking'].browse(bu_pick_vals['res_id'])
+ _force_locations(new_pick, BU_STOCK_LOCATION_ID, BU_OUTPUT_LOCATION_ID)
+
new_pick.write({
- 'location_id': BU_STOCK_LOCATION_ID,
- 'location_dest_id': BU_OUTPUT_LOCATION_ID,
'group_id': bu_out.group_id.id,
'tukar_guling_id': record.id,
'sale_order': record.origin
})
- new_pick.action_assign() # Penting agar bisa trigger check koli
+ new_pick.action_assign()
new_pick.action_confirm()
created_returns.append(new_pick)
_logger.info(f"✅ BU/PICK Baru dari ORT created: {new_pick.name}")
@@ -810,14 +906,13 @@ class TukarGuling(models.Model):
}).create({
'picking_id': srt_picking.id,
'location_id': BU_OUTPUT_LOCATION_ID,
- 'original_location_id': PARTNER_LOCATION_ID,
'product_return_moves': return_lines
})
bu_out_vals = bu_out_wizard.create_returns()
new_out = self.env['stock.picking'].browse(bu_out_vals['res_id'])
+ _force_locations(new_out, BU_OUTPUT_LOCATION_ID, PARTNER_LOCATION_ID)
+
new_out.write({
- 'location_id': BU_OUTPUT_LOCATION_ID,
- 'location_dest_id': PARTNER_LOCATION_ID,
'group_id': bu_out.group_id.id,
'tukar_guling_id': record.id,
'sale_order': record.origin