From 4925cc8bfdc412f22e84b07a446a2a08bd20b2b0 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Thu, 14 May 2026 16:40:23 +0200 Subject: [PATCH] Bug create contract --- modules/purchase_trade/dashboard.py | 8 +- modules/purchase_trade/lot.py | 492 ++++++++++++++-------------- modules/purchase_trade/service.py | 95 ++++++ 3 files changed, 354 insertions(+), 241 deletions(-) diff --git a/modules/purchase_trade/dashboard.py b/modules/purchase_trade/dashboard.py index ef97d8c..405933c 100755 --- a/modules/purchase_trade/dashboard.py +++ b/modules/purchase_trade/dashboard.py @@ -749,7 +749,11 @@ class BotWizard(Wizard): lms.lot_r_id = ls.getLotQt()[0].id lms.lot_matched_qt = lp.lot_quantity lms.lot_sale = s - LotQt.match_lots([lmp],[lms]) + Lot = Pool().get('lot.lot') + with Lot.skip_quantity_consistency(): + LotQt.match_lots([lmp],[lms]) + Lot.assert_lines_quantity_consistency( + [p.lines[0], s.lines[0]]) Sale._process_shipment([s]) elif do_ == "add": @@ -876,4 +880,4 @@ class BotAction(ModelSQL, ModelView): @classmethod def state_dashboard(cls): - return 1 \ No newline at end of file + return 1 diff --git a/modules/purchase_trade/lot.py b/modules/purchase_trade/lot.py index 17f79c9..416bc8b 100755 --- a/modules/purchase_trade/lot.py +++ b/modules/purchase_trade/lot.py @@ -230,6 +230,11 @@ class Lot(metaclass=PoolMeta): if getattr(lot, 'sale_line', None): lines.append(lot.sale_line) cls.assert_lines_quantity_consistency(lines) + + @staticmethod + def skip_quantity_consistency(): + return Transaction().set_context( + _purchase_trade_skip_quantity_consistency=True) line = fields.Many2One('purchase.line',"Purchase",ondelete='CASCADE') move = fields.Function(fields.Many2One('stock.move',"Move"),'get_current_move') @@ -1308,8 +1313,7 @@ class LotQt( lqt.lot_p.line.unit))) tot_qt = round(tot_qt, 5) cls._warn_physical_lot_tolerance(lqt, vlots) - with Transaction().set_context( - _purchase_trade_skip_quantity_consistency=True): + with Lot.skip_quantity_consistency(): # Consume the forecast first. Physical lot saves recompute the # virtual lot, so the matching lot.qt must already carry the # target open quantity during the rest of the transaction. @@ -2656,72 +2660,73 @@ class LotShipping(Wizard): affected_lines = [] if not self.ship.shipment: pass - for r in self.records: - if r.r_lot_shipment_in: - raise UserError("Please unlink before linking to a new shipment !") - else: - shipped_quantity = Decimal(str(r.r_lot_quantity)).quantize(Decimal("0.00001")) - logger.info("LotShipping:%s",shipped_quantity) - shipment_origin = None - if self.ship.quantity: - shipped_quantity = self.ship.quantity - if shipped_quantity == 0: - shipped_quantity = Decimal(str(r.r_lot_matched)).quantize(Decimal("0.00001")) - if self.ship.shipment == 'in': - if not self.ship.shipment_in: - UserError("Shipment not known!") - shipment_origin = 'stock.shipment.in,'+str(self.ship.shipment_in.id) - elif self.ship.shipment == 'out': - if not self.ship.shipment_out: - UserError("Shipment not known!") - shipment_origin = 'stock.shipment.out,'+str(self.ship.shipment_out.id) - elif self.ship.shipment == 'int': - if not self.ship.shipment_internal: - UserError("Shipment not known!") - shipment_origin = 'stock.shipment.internal,'+str(self.ship.shipment_internal.id) - if r.id < 10000000 : - l = Lot(r.id) - logger.info("IN_SHIPPING1:%s",l) - if self.ship.shipment == 'in': - l.lot_shipment_in = self.ship.shipment_in - elif self.ship.shipment == 'out': - l.lot_shipment_out = self.ship.shipment_out - elif self.ship.shipment == 'int': - l.lot_shipment_internal = self.ship.shipment_internal - logger.info("IN_SHIPPING2:%s",l.move) - if not l.move: - continue - logger.info("IN_SHIPPING3:%s",r) - move = Move(l.move) - move.shipment = shipment_origin - Move.save([move]) - linked_transit_move = move.get_linked_transit_move() - if linked_transit_move: - linked_transit_move.shipment = shipment_origin - Move.save([linked_transit_move]) - #Decrease forecasted virtual part shipped - vlot_p = l.getVlot_p() - l.updateVirtualPart(-l.get_current_quantity_converted(),shipment_origin,l.getVlot_s()) - l.lot_av = 'reserved' - Lot.save([l]) - l.set_current_quantity(l.lot_quantity,l.lot_gross_quantity,2) - Lot.save([l]) - if l.line: - affected_lines.append(l.line) - if l.sale_line: - affected_lines.append(l.sale_line) + with Lot.skip_quantity_consistency(): + for r in self.records: + if r.r_lot_shipment_in: + raise UserError("Please unlink before linking to a new shipment !") else: - lqt = LotQt(r.id - 10000000) - #Increase forecasted virtual part shipped - if not lqt.lot_p.updateVirtualPart(shipped_quantity,shipment_origin,lqt.lot_s): - logger.info("LotShipping2:%s",shipped_quantity) - lqt.lot_p.createVirtualPart(shipped_quantity,shipment_origin,lqt.lot_s) - #Decrease forecasted virtual part non shipped - lqt.lot_p.updateVirtualPart(-shipped_quantity,None,lqt.lot_s) - if lqt.lot_p and lqt.lot_p.line: - affected_lines.append(lqt.lot_p.line) - if lqt.lot_s and lqt.lot_s.sale_line: - affected_lines.append(lqt.lot_s.sale_line) + shipped_quantity = Decimal(str(r.r_lot_quantity)).quantize(Decimal("0.00001")) + logger.info("LotShipping:%s",shipped_quantity) + shipment_origin = None + if self.ship.quantity: + shipped_quantity = self.ship.quantity + if shipped_quantity == 0: + shipped_quantity = Decimal(str(r.r_lot_matched)).quantize(Decimal("0.00001")) + if self.ship.shipment == 'in': + if not self.ship.shipment_in: + UserError("Shipment not known!") + shipment_origin = 'stock.shipment.in,'+str(self.ship.shipment_in.id) + elif self.ship.shipment == 'out': + if not self.ship.shipment_out: + UserError("Shipment not known!") + shipment_origin = 'stock.shipment.out,'+str(self.ship.shipment_out.id) + elif self.ship.shipment == 'int': + if not self.ship.shipment_internal: + UserError("Shipment not known!") + shipment_origin = 'stock.shipment.internal,'+str(self.ship.shipment_internal.id) + if r.id < 10000000 : + l = Lot(r.id) + logger.info("IN_SHIPPING1:%s",l) + if self.ship.shipment == 'in': + l.lot_shipment_in = self.ship.shipment_in + elif self.ship.shipment == 'out': + l.lot_shipment_out = self.ship.shipment_out + elif self.ship.shipment == 'int': + l.lot_shipment_internal = self.ship.shipment_internal + logger.info("IN_SHIPPING2:%s",l.move) + if not l.move: + continue + logger.info("IN_SHIPPING3:%s",r) + move = Move(l.move) + move.shipment = shipment_origin + Move.save([move]) + linked_transit_move = move.get_linked_transit_move() + if linked_transit_move: + linked_transit_move.shipment = shipment_origin + Move.save([linked_transit_move]) + #Decrease forecasted virtual part shipped + vlot_p = l.getVlot_p() + l.updateVirtualPart(-l.get_current_quantity_converted(),shipment_origin,l.getVlot_s()) + l.lot_av = 'reserved' + Lot.save([l]) + l.set_current_quantity(l.lot_quantity,l.lot_gross_quantity,2) + Lot.save([l]) + if l.line: + affected_lines.append(l.line) + if l.sale_line: + affected_lines.append(l.sale_line) + else: + lqt = LotQt(r.id - 10000000) + #Increase forecasted virtual part shipped + if not lqt.lot_p.updateVirtualPart(shipped_quantity,shipment_origin,lqt.lot_s): + logger.info("LotShipping2:%s",shipped_quantity) + lqt.lot_p.createVirtualPart(shipped_quantity,shipment_origin,lqt.lot_s) + #Decrease forecasted virtual part non shipped + lqt.lot_p.updateVirtualPart(-shipped_quantity,None,lqt.lot_s) + if lqt.lot_p and lqt.lot_p.line: + affected_lines.append(lqt.lot_p.line) + if lqt.lot_s and lqt.lot_s.sale_line: + affected_lines.append(lqt.lot_s.sale_line) Lot.assert_lines_quantity_consistency(affected_lines) return 'end' @@ -2784,13 +2789,14 @@ class LotMatching(Wizard): def transition_matching(self): Warning = Pool().get('res.user.warning') LotQt = Pool().get('lot.qt') - if self.match.tot_p != self.match.tot_s: - warning_name = Warning.format("Quantity's issue", []) - if Warning.check(warning_name): - raise QtWarning(warning_name, - "Quantities not compatibles") + if self.match.tot_p != self.match.tot_s: + warning_name = Warning.format("Quantity's issue", []) + if Warning.check(warning_name): + raise QtWarning(warning_name, + "Quantities not compatibles") Lot = Pool().get('lot.lot') - LotQt.match_lots(self.match.lot_p,self.match.lot_s) + with Lot.skip_quantity_consistency(): + LotQt.match_lots(self.match.lot_p,self.match.lot_s) affected_lines = [] for matched_lot in list(self.match.lot_p or []) + list(self.match.lot_s or []): if matched_lot.lot_id: @@ -2974,42 +2980,43 @@ class LotUnmatch(Wizard): Lot = Pool().get('lot.lot') LotQt = Pool().get('lot.qt') affected_lines = [] - for r in self.records: - if r.id > 10000000 : - lqt = LotQt(r.id - 10000000) - if lqt.lot_p and lqt.lot_s: - lot = lqt.lot_p - qt = lqt.lot_quantity - #Increase forecasted virtual parts non matched - if not lot.updateVirtualPart(qt,lqt.lot_shipment_origin,None): - lot.createVirtualPart(qt,lqt.lot_shipment_origin,None) - if not lot.updateVirtualPart(qt,lqt.lot_shipment_origin,lqt.lot_s,'only sale'): - lot.createVirtualPart(qt,lqt.lot_shipment_origin,lqt.lot_s,'only sale') - #Decrease forecasted virtual part matched - logger.info("UNMATCH_QT:%s",qt) - lot.updateVirtualPart(-qt,lqt.lot_shipment_origin,lqt.lot_s) - if lqt.lot_p and lqt.lot_p.line: - affected_lines.append(lqt.lot_p.line) - if lqt.lot_s and lqt.lot_s.sale_line: - affected_lines.append(lqt.lot_s.sale_line) - else: - lot = Lot(r.id) - qt = lot.get_current_quantity_converted() - # #Increase forecasted virtual part matched - # if not lot.updateVirtualPart(qt,lot.lot_shipment_origin,lot.getVlot_s()): - # lot.createVirtualPart(qt,lot.lot_shipment_origin,lot.getVlot_s()) - # #Decrease forecasted virtual part non matched - # if not lot.updateVirtualPart(-qt,lot.lot_shipment_origin,None): - # lot.createVirtualPart(-qt,lot.lot_shipment_origin,None) - ls = lot.getVlot_s() - ls.lot_quantity += qt - Lot.save([ls]) - lot.sale_line = None - Lot.save([lot]) - if lot.line: - affected_lines.append(lot.line) - if ls.sale_line: - affected_lines.append(ls.sale_line) + with Lot.skip_quantity_consistency(): + for r in self.records: + if r.id > 10000000 : + lqt = LotQt(r.id - 10000000) + if lqt.lot_p and lqt.lot_s: + lot = lqt.lot_p + qt = lqt.lot_quantity + #Increase forecasted virtual parts non matched + if not lot.updateVirtualPart(qt,lqt.lot_shipment_origin,None): + lot.createVirtualPart(qt,lqt.lot_shipment_origin,None) + if not lot.updateVirtualPart(qt,lqt.lot_shipment_origin,lqt.lot_s,'only sale'): + lot.createVirtualPart(qt,lqt.lot_shipment_origin,lqt.lot_s,'only sale') + #Decrease forecasted virtual part matched + logger.info("UNMATCH_QT:%s",qt) + lot.updateVirtualPart(-qt,lqt.lot_shipment_origin,lqt.lot_s) + if lqt.lot_p and lqt.lot_p.line: + affected_lines.append(lqt.lot_p.line) + if lqt.lot_s and lqt.lot_s.sale_line: + affected_lines.append(lqt.lot_s.sale_line) + else: + lot = Lot(r.id) + qt = lot.get_current_quantity_converted() + # #Increase forecasted virtual part matched + # if not lot.updateVirtualPart(qt,lot.lot_shipment_origin,lot.getVlot_s()): + # lot.createVirtualPart(qt,lot.lot_shipment_origin,lot.getVlot_s()) + # #Decrease forecasted virtual part non matched + # if not lot.updateVirtualPart(-qt,lot.lot_shipment_origin,None): + # lot.createVirtualPart(-qt,lot.lot_shipment_origin,None) + ls = lot.getVlot_s() + ls.lot_quantity += qt + Lot.save([ls]) + lot.sale_line = None + Lot.save([lot]) + if lot.line: + affected_lines.append(lot.line) + if ls.sale_line: + affected_lines.append(ls.sale_line) Lot.assert_lines_quantity_consistency(affected_lines) return 'end' @@ -3027,57 +3034,58 @@ class LotUnship(Wizard): LotQt = Pool().get('lot.qt') Move = Pool().get('stock.move') affected_lines = [] - for r in self.records: - lqt = LotQt(r.id - 10000000) - lot = Lot(r.id) - if lqt and r.r_lot_type == 'virtual': - shipment = None - qt = lqt.lot_quantity - mode = 'both' - if not lqt.lot_p: - lot = lqt.lot_s - mode = 'only sale' - else: - lot = lqt.lot_p - if lqt.lot_shipment_in: - shipment = lqt.lot_shipment_in - elif lqt.lot_shipment_internal: - shipment = lqt.lot_shipment_internal - elif lqt.lot_shipment_out: - shipment = lqt.lot_shipment_out - #Decrease forecasted virtual part shipped - lot.updateVirtualPart(-qt,lqt.lot_shipment_origin,lqt.lot_s,mode) - #Increase forecasted virtual part non shipped - if not lot.updateVirtualPart(qt,None,lqt.lot_s,mode): - lot.createVirtualPart(qt,None,lqt.lot_s,mode) - if lqt.lot_p and lqt.lot_p.line: - affected_lines.append(lqt.lot_p.line) - if lqt.lot_s and lqt.lot_s.sale_line: - affected_lines.append(lqt.lot_s.sale_line) - if lot and r.r_lot_type == 'physic': - lot = Lot(r.r_lot_p) - if lot.lot_type == 'physic': - str_sh = str(lot.lot_shipment_origin) - if 'stock.shipment.in' in str_sh: - lot.lot_shipment_in = None - elif 'stock.shipment.out' in str_sh: - lot.lot_shipment_out = None - elif 'stock.shipment.internal' in str_sh: - lot.lot_shipment_internal = None - - if lot.move: - move = Move(lot.move) - move.shipment = None - Move.save([move]) - # lm = LotMove.search([('lot','=',lot.id),('move','=',lot.move)]) - # if lm: - # LotMove.delete(lm) - lot.lot_status = 'forecast' - Lot.save([lot]) - if lot.line: - affected_lines.append(lot.line) - if lot.sale_line: - affected_lines.append(lot.sale_line) + with Lot.skip_quantity_consistency(): + for r in self.records: + lqt = LotQt(r.id - 10000000) + lot = Lot(r.id) + if lqt and r.r_lot_type == 'virtual': + shipment = None + qt = lqt.lot_quantity + mode = 'both' + if not lqt.lot_p: + lot = lqt.lot_s + mode = 'only sale' + else: + lot = lqt.lot_p + if lqt.lot_shipment_in: + shipment = lqt.lot_shipment_in + elif lqt.lot_shipment_internal: + shipment = lqt.lot_shipment_internal + elif lqt.lot_shipment_out: + shipment = lqt.lot_shipment_out + #Decrease forecasted virtual part shipped + lot.updateVirtualPart(-qt,lqt.lot_shipment_origin,lqt.lot_s,mode) + #Increase forecasted virtual part non shipped + if not lot.updateVirtualPart(qt,None,lqt.lot_s,mode): + lot.createVirtualPart(qt,None,lqt.lot_s,mode) + if lqt.lot_p and lqt.lot_p.line: + affected_lines.append(lqt.lot_p.line) + if lqt.lot_s and lqt.lot_s.sale_line: + affected_lines.append(lqt.lot_s.sale_line) + if lot and r.r_lot_type == 'physic': + lot = Lot(r.r_lot_p) + if lot.lot_type == 'physic': + str_sh = str(lot.lot_shipment_origin) + if 'stock.shipment.in' in str_sh: + lot.lot_shipment_in = None + elif 'stock.shipment.out' in str_sh: + lot.lot_shipment_out = None + elif 'stock.shipment.internal' in str_sh: + lot.lot_shipment_internal = None + + if lot.move: + move = Move(lot.move) + move.shipment = None + Move.save([move]) + # lm = LotMove.search([('lot','=',lot.id),('move','=',lot.move)]) + # if lm: + # LotMove.delete(lm) + lot.lot_status = 'forecast' + Lot.save([lot]) + if lot.line: + affected_lines.append(lot.line) + if lot.sale_line: + affected_lines.append(lot.sale_line) Lot.assert_lines_quantity_consistency(affected_lines) return 'end' @@ -3201,56 +3209,61 @@ class LotImporting(Wizard): def transition_start(self): return 'imp' - def transition_importing(self): - if self.records: - line = self.records[0].r_line - Lot = Pool().get('lot.lot') - LotQt = Pool().get('lot.qt') + def transition_importing(self): + if self.records: + line = self.records[0].r_line + Lot = Pool().get('lot.lot') + LotQt = Pool().get('lot.qt') LotQtHist = Pool().get('lot.qt.hist') - LotQtType = Pool().get('lot.qt.type') - lqt = LotQt(self.records[0].id - 10000000) - tot_qt = 0 - for l in self.imp.lots: - l.line = line - l.lot_shipment_in = self.records[0].r_lot_shipment_in - l.lot_shipment_internal = self.records[0].r_lot_shipment_internal - l.lot_shipment_out = self.records[0].r_lot_shipment_out - l.lot_product = self.records[0].r_lot_product - l.sale_line = self.records[0].r_sale_line - l.lot_type = 'physic' - premium = Decimal(0) - if l.lot_price: - premium = l.lot_price - l.line.unit_price - logger.info("IMPORTLOT:%s",l.line.linked_price) - if l.line.linked_price: - premium = l.lot_price - l.line.linked_price - l.lot_premium = premium - l.lot_premium_sup = premium - Uom = Pool().get('product.uom') - net_qt = round(Decimal(Uom.compute_qty(l.lot_unit_line, float(l.lot_quantity), line.unit)),5) - gross_qt = round(Decimal(Uom.compute_qty(l.lot_unit_line, float(l.lot_gross_quantity), line.unit)),5) - lqtt = LotQtType.search([('sequence','=',1)]) - if lqtt: - lqh = LotQtHist() - lqh.quantity_type = lqtt[0] - lqh.quantity = l.lot_quantity - lqh.gross_quantity = l.lot_gross_quantity - l.lot_hist = [lqh] - Lot.save([l]) - tot_qt += net_qt - logger.info("IMPORTING:%s",net_qt) - #need to decrease sale virtual lot if matching - if self.records[0].r_lot_s: - ls = Lot(self.records[0].r_lot_s) - ls.lot_quantity -= net_qt - ls.lot_gross_quantity -= net_qt - ls.lot_qt -= float(net_qt) - Lot.save([ls]) - lqt.lot_quantity -= tot_qt - if lqt.lot_quantity < 0: - lqt.lot_quantity = 0 - LotQt.save([lqt]) - return 'end' + LotQtType = Pool().get('lot.qt.type') + lqt = LotQt(self.records[0].id - 10000000) + tot_qt = 0 + affected_lines = [line] + if self.records[0].r_sale_line: + affected_lines.append(self.records[0].r_sale_line) + with Lot.skip_quantity_consistency(): + for l in self.imp.lots: + l.line = line + l.lot_shipment_in = self.records[0].r_lot_shipment_in + l.lot_shipment_internal = self.records[0].r_lot_shipment_internal + l.lot_shipment_out = self.records[0].r_lot_shipment_out + l.lot_product = self.records[0].r_lot_product + l.sale_line = self.records[0].r_sale_line + l.lot_type = 'physic' + premium = Decimal(0) + if l.lot_price: + premium = l.lot_price - l.line.unit_price + logger.info("IMPORTLOT:%s",l.line.linked_price) + if l.line.linked_price: + premium = l.lot_price - l.line.linked_price + l.lot_premium = premium + l.lot_premium_sup = premium + Uom = Pool().get('product.uom') + net_qt = round(Decimal(Uom.compute_qty(l.lot_unit_line, float(l.lot_quantity), line.unit)),5) + gross_qt = round(Decimal(Uom.compute_qty(l.lot_unit_line, float(l.lot_gross_quantity), line.unit)),5) + lqtt = LotQtType.search([('sequence','=',1)]) + if lqtt: + lqh = LotQtHist() + lqh.quantity_type = lqtt[0] + lqh.quantity = l.lot_quantity + lqh.gross_quantity = l.lot_gross_quantity + l.lot_hist = [lqh] + Lot.save([l]) + tot_qt += net_qt + logger.info("IMPORTING:%s",net_qt) + #need to decrease sale virtual lot if matching + if self.records[0].r_lot_s: + ls = Lot(self.records[0].r_lot_s) + ls.lot_quantity -= net_qt + ls.lot_gross_quantity -= net_qt + ls.lot_qt -= float(net_qt) + Lot.save([ls]) + lqt.lot_quantity -= tot_qt + if lqt.lot_quantity < 0: + lqt.lot_quantity = 0 + LotQt.save([lqt]) + Lot.assert_lines_quantity_consistency(affected_lines) + return 'end' def end(self): return 'reload' @@ -3823,36 +3836,37 @@ class LotWeighing(Wizard): Lot = Pool().get('lot.lot') LotHist = Pool().get('lot.qt.hist') affected_lines = [] - for l in self.w.lot_p: - quantity = l.lot.get_current_quantity_converted() - lhs = LotHist.search([('lot',"=",l.lot.id),('quantity_type','=',self.w.lot_state.id)]) - if lhs: - lh = lhs[0] - else: - lh = LotHist() - lh.lot = l.lot - lh.quantity_type = self.w.lot_state - lh.quantity = round(l.lot_quantity_new,5) - lh.gross_quantity = round(l.lot_gross_quantity_new,5) - LotHist.save([lh]) - l.lot.lot_qt = l.lot_qt - - if self.w.lot_update_state : - l.lot.lot_state = self.w.lot_state - Lot.save([l.lot]) - diff = round(Decimal(l.lot.get_current_quantity_converted() - quantity),5) - if diff != 0 : - #need to update virtual part - l.lot.updateVirtualPart(-diff,l.lot.lot_shipment_origin,l.lot.getVlot_s()) - if l.lot.line: - affected_lines.append(l.lot.line) - if l.lot.sale_line: - affected_lines.append(l.lot.sale_line) - #adjuts fee ordered with new quantity - fees = FeeLots.search(['lot','=',l.lot.id]) - for f in fees: - f.fee.sync_quantity_from_lots() - f.fee.adjust_purchase_values() + with Lot.skip_quantity_consistency(): + for l in self.w.lot_p: + quantity = l.lot.get_current_quantity_converted() + lhs = LotHist.search([('lot',"=",l.lot.id),('quantity_type','=',self.w.lot_state.id)]) + if lhs: + lh = lhs[0] + else: + lh = LotHist() + lh.lot = l.lot + lh.quantity_type = self.w.lot_state + lh.quantity = round(l.lot_quantity_new,5) + lh.gross_quantity = round(l.lot_gross_quantity_new,5) + LotHist.save([lh]) + l.lot.lot_qt = l.lot_qt + + if self.w.lot_update_state : + l.lot.lot_state = self.w.lot_state + Lot.save([l.lot]) + diff = round(Decimal(l.lot.get_current_quantity_converted() - quantity),5) + if diff != 0 : + #need to update virtual part + l.lot.updateVirtualPart(-diff,l.lot.lot_shipment_origin,l.lot.getVlot_s()) + if l.lot.line: + affected_lines.append(l.lot.line) + if l.lot.sale_line: + affected_lines.append(l.lot.sale_line) + #adjuts fee ordered with new quantity + fees = FeeLots.search(['lot','=',l.lot.id]) + for f in fees: + f.fee.sync_quantity_from_lots() + f.fee.adjust_purchase_values() Lot.assert_lines_quantity_consistency(affected_lines) return 'end' diff --git a/modules/purchase_trade/service.py b/modules/purchase_trade/service.py index 20fb8f7..6562ea7 100644 --- a/modules/purchase_trade/service.py +++ b/modules/purchase_trade/service.py @@ -340,18 +340,113 @@ class ContractFactory: qt = source['quantity'] if type_ == 'Purchase': + if cls._attach_or_split_source_lot_qt(source, lot, type_): + return if not lot.updateVirtualPart(qt, shipment_origin, vlot): lot.createVirtualPart(qt, shipment_origin, vlot) # Decrease forecasted virtual part non matched lot.updateVirtualPart(-qt, shipment_origin, vlot, 'only sale') else: + if cls._attach_or_split_source_lot_qt(source, lot, type_): + return if not vlot.updateVirtualPart(qt, shipment_origin, lot): vlot.createVirtualPart(qt, shipment_origin, lot) # Decrease forecasted virtual part non matched vlot.updateVirtualPart(-qt, shipment_origin, None) + @classmethod + def _attach_or_split_source_lot_qt(cls, source, new_virtual_lot, type_): + """Reuse the selected forecast line instead of create/decrease.""" + pool = Pool() + LotQt = pool.get('lot.qt') + + quantity = cls._normalize_quantity(source['quantity']) + source_lqt = source.get('lqt') + if not source_lqt: + source_lqt = cls._find_source_lot_qt(source, type_) + if not source_lqt: + return False + + remaining = cls._normalize_quantity(source_lqt.lot_quantity) - quantity + if remaining < 0: + return False + + if remaining: + matched_lqt = LotQt() + matched_lqt.lot_p = ( + new_virtual_lot.id + if type_ == 'Purchase' + else source_lqt.lot_p) + matched_lqt.lot_s = ( + source_lqt.lot_s + if type_ == 'Purchase' + else new_virtual_lot.id) + matched_lqt.lot_shipment_in = source_lqt.lot_shipment_in + matched_lqt.lot_shipment_internal = source_lqt.lot_shipment_internal + matched_lqt.lot_shipment_out = source_lqt.lot_shipment_out + matched_lqt.lot_move = source_lqt.lot_move + matched_lqt.lot_av = source_lqt.lot_av + matched_lqt.lot_status = source_lqt.lot_status + matched_lqt.lot_unit = source_lqt.lot_unit + matched_lqt.lot_quantity = quantity + source_lqt.lot_quantity = remaining + LotQt.save([source_lqt, matched_lqt]) + else: + if type_ == 'Purchase': + source_lqt.lot_p = new_virtual_lot.id + else: + source_lqt.lot_s = new_virtual_lot.id + LotQt.save([source_lqt]) + return True + + @classmethod + def _find_source_lot_qt(cls, source, type_): + pool = Pool() + LotQt = pool.get('lot.qt') + lot = source.get('lot') + if not lot: + return None + + domain = [] + if type_ == 'Purchase': + domain.extend([ + ('lot_s', '=', lot.id), + ('lot_p', '=', None), + ]) + else: + domain.extend([ + ('lot_p', '=', lot.id), + ('lot_s', '=', None), + ]) + cls._add_shipment_domain(domain, source.get('shipment_origin')) + domain.append(('lot_quantity', '!=', 0)) + lqts = LotQt.search(domain) + quantity = cls._normalize_quantity(source['quantity']) + for lqt in lqts: + if cls._normalize_quantity(lqt.lot_quantity) >= quantity: + return lqt + return None + + @staticmethod + def _add_shipment_domain(domain, shipment_origin): + domain.extend([ + ('lot_shipment_in', '=', None), + ('lot_shipment_internal', '=', None), + ('lot_shipment_out', '=', None), + ]) + if not shipment_origin: + return + model, record_id = shipment_origin.split(',', 1) + record_id = int(record_id) + if model == 'stock.shipment.in': + domain[-3] = ('lot_shipment_in', '=', record_id) + elif model == 'stock.shipment.internal': + domain[-2] = ('lot_shipment_internal', '=', record_id) + elif model == 'stock.shipment.out': + domain[-1] = ('lot_shipment_out', '=', record_id) + @staticmethod def _get_shipment_origin(ct): if ct.shipment_in: