diff options
| author | Indoteknik . <it@fixcomart.co.id> | 2025-08-08 15:11:35 +0700 |
|---|---|---|
| committer | Indoteknik . <it@fixcomart.co.id> | 2025-08-08 15:11:35 +0700 |
| commit | da1903796553ca0ee5195a7962521bc09b4b98a8 (patch) | |
| tree | 0fe68309a748a74e121c9f9c51cb4b047e7cd217 /indoteknik_custom/models/tukar_guling.py | |
| parent | 7ebcf1675e55dae39f769466e5bf5ef064646f27 (diff) | |
| parent | 19067dfea850b289b47d70ab36628b796ae87265 (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.py | 197 |
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 |
