diff --git a/modules/purchase_trade/service.py b/modules/purchase_trade/service.py index 52b420b..d561868 100644 --- a/modules/purchase_trade/service.py +++ b/modules/purchase_trade/service.py @@ -344,6 +344,33 @@ class ContractFactory: requested = sum( cls._normalize_quantity(contract.quantity) for contract in contracts) + logger.info( + "CONTRACT_FACTORY_VALIDATE_QUANTITY matched=%s requested=%s " + "available=%s ct_lot=%s ct_quantity=%s active_ids=%s " + "contracts=%s sources=%s", + getattr(ct, 'matched', None), requested, available, + getattr(getattr(ct, 'lot', None), 'id', None), + getattr(ct, 'quantity', None), + (Transaction().context or {}).get('active_ids'), + [ + { + 'quantity': cls._normalize_quantity(contract.quantity), + 'party': getattr(getattr(contract, 'party', None), 'id', None), + 'unit': getattr(getattr(contract, 'unit', None), 'id', None), + 'price_type': getattr(contract, 'price_type', None), + } + for contract in contracts + ], + [ + { + 'quantity': source['quantity'], + 'lot': getattr(source.get('lot'), 'id', None), + 'lqt': getattr(source.get('lqt'), 'id', None), + 'trade_line': getattr(source.get('trade_line'), 'id', None), + 'shipment_origin': source.get('shipment_origin'), + } + for source in sources + ]) if requested > available: raise UserError( 'The requested quantity exceeds the selected open quantity.') diff --git a/modules/purchase_trade/stock.py b/modules/purchase_trade/stock.py index 1e3321e..acea68d 100755 --- a/modules/purchase_trade/stock.py +++ b/modules/purchase_trade/stock.py @@ -2397,9 +2397,9 @@ class ShipmentIn(metaclass=PoolMeta): # return "\n".join(lines) - def _create_lots_from_fintrade(self): - t = Table('freight_booking_lots') - cursor = Transaction().connection.cursor() + def _create_lots_from_fintrade(self): + t = Table('freight_booking_lots') + cursor = Transaction().connection.cursor() query = t.select( t.BOOKING_NUMBER, t.LOT_NUMBER, @@ -2421,14 +2421,18 @@ class ShipmentIn(metaclass=PoolMeta): t.SHIPMENT_CHUNK_KEY, where=(t.BOOKING_NUMBER == int(self.reference)) ) - cursor.execute(*query) - rows = cursor.fetchall() - logger.info("ROWS:%s",rows) - inv_date = None - inv_nb = None - if rows: - sale_line = None - for row in rows: + cursor.execute(*query) + rows = cursor.fetchall() + logger.info( + "FINTRADE_LOTS_START shipment=%s reference=%s bl=%s rows=%s", + getattr(self, 'id', None), self.reference, self.bl_number, + len(rows)) + logger.info("ROWS:%s",rows) + inv_date = None + inv_nb = None + if rows: + sale_line = None + for row in rows: logger.info("ROW:%s",row) #Purchase & Sale creation LotQt = Pool().get('lot.qt') @@ -2438,11 +2442,21 @@ class ShipmentIn(metaclass=PoolMeta): Product = Pool().get('product.product') Party = Pool().get('party.party') Uom = Pool().get('product.uom') - Sale = Pool().get('sale.sale') - SaleLine = Pool().get('sale.line') - dec_key = str(row[16]).strip() - chunk_key = str(row[17]).strip() - lot_unit = str(row[5]).strip().lower() + Sale = Pool().get('sale.sale') + SaleLine = Pool().get('sale.line') + dec_key = str(row[16]).strip() + chunk_key = str(row[17]).strip() + chunk_key_int = None + if chunk_key and chunk_key.lower() not in {'none', 'null'}: + try: + chunk_key_int = int(chunk_key) + except (TypeError, ValueError): + logger.info( + "FINTRADE_INVALID_CHUNK_KEY shipment=%s " + "booking=%s declaration=%s chunk=%s", + getattr(self, 'id', None), self.reference, + dec_key, chunk_key) + lot_unit = str(row[5]).strip().lower() product = str(row[6]).strip().upper() lot_net_weight = Decimal(row[4]) logger.info("LOT_NET_WEIGHT:%s",lot_net_weight) @@ -2455,18 +2469,84 @@ class ShipmentIn(metaclass=PoolMeta): inv_date = str(row[13]).strip() inv_nb = str(row[11]).strip() sell_price = Decimal(row[10]) - premium = Decimal(row[14]) - reference = Decimal(row[15]) - logger.info("DECLARATION_KEY:%s",dec_key) - declaration = SaleLine.search(['note','=',dec_key]) - if declaration: - sale_line = declaration[0] - logger.info("WITH_DEC:%s",sale_line) - vlot = sale_line.lots[0] - lqt = LotQt.search([('lot_s','=',vlot.id)]) - if lqt: - for lq in lqt: - if lq.lot_p: + premium = Decimal(row[14]) + reference = Decimal(row[15]) + logger.info( + "FINTRADE_LOT_ROW shipment=%s booking=%s lot_number=%s " + "chunk=%s declaration=%s net=%s gross=%s bales=%s " + "lot_unit=%s sell_currency=%s sell_unit=%s customer=%s " + "sale_contract=%s", + getattr(self, 'id', None), self.reference, lot_number, + chunk_key, dec_key, lot_net_weight, lot_gross_weight, + lot_bales, lot_unit, sell_price_currency, + sell_price_unit, customer, reference) + if chunk_key_int is not None: + existing_chunk_lots = Lot.search([ + ('lot_chunk_key', '=', chunk_key_int), + ]) + logger.info( + "FINTRADE_CHUNK_CHECK chunk=%s existing_lots=%s", + chunk_key, + [ + { + 'id': lot.id, + 'name': getattr(lot, 'lot_name', None), + 'shipment_in': ( + lot.lot_shipment_in.id + if getattr(lot, 'lot_shipment_in', None) + else None), + 'purchase_line': ( + lot.line.id + if getattr(lot, 'line', None) + else None), + 'sale_line': ( + lot.sale_line.id + if getattr(lot, 'sale_line', None) + else None), + } + for lot in existing_chunk_lots + ]) + logger.info("DECLARATION_KEY:%s",dec_key) + declaration = SaleLine.search(['note','=',dec_key]) + logger.info( + "FINTRADE_DECLARATION_SEARCH declaration=%s count=%s " + "sale_line_ids=%s", + dec_key, len(declaration), [d.id for d in declaration]) + if declaration: + sale_line = declaration[0] + logger.info( + "FINTRADE_DECLARATION_REUSE declaration=%s " + "sale_line=%s sale=%s quantity=%s theoretical=%s " + "lot_ids=%s", + dec_key, sale_line.id, + sale_line.sale.id if sale_line.sale else None, + sale_line.quantity, sale_line.quantity_theorical, + [lot.id for lot in sale_line.lots]) + vlot = sale_line.lots[0] + lqt = LotQt.search([('lot_s','=',vlot.id)]) + logger.info( + "FINTRADE_DECLARATION_LQT declaration=%s vlot=%s " + "vlot_qty=%s lqt_count=%s lqts=%s", + dec_key, vlot.id, vlot.lot_quantity, len(lqt), + [ + { + 'id': q.id, + 'lot_p': q.lot_p.id if q.lot_p else None, + 'lot_s': q.lot_s.id if q.lot_s else None, + 'quantity': q.lot_quantity, + 'unit': q.lot_unit.id if q.lot_unit else None, + 'shipment_in': ( + q.lot_shipment_in.id + if q.lot_shipment_in else None), + 'shipment_origin': q.lot_shipment_origin, + 'status': q.lot_status, + 'availability': q.lot_av, + } + for q in lqt + ]) + if lqt: + for lq in lqt: + if lq.lot_p: logger.info("VLOT_P:%s",lq.lot_p) sale_line.quantity_theorical += round(lot_net_weight,2) SaleLine.save([sale_line]) @@ -2511,12 +2591,33 @@ class ShipmentIn(metaclass=PoolMeta): sale_line.created_by_code = False sale_line.note = dec_key SaleLine.save([sale_line]) - - #need to link the virtual part to the shipment - lqt = LotQt.search([('lot_s','=',sale_line.lots[0])]) - if lqt: - lqt[0].lot_shipment_in = self - LotQt.save(lqt) + + #need to link the virtual part to the shipment + lqt = LotQt.search([('lot_s','=',sale_line.lots[0])]) + logger.info( + "FINTRADE_NEW_SALE_LQT sale=%s sale_line=%s " + "vlot=%s vlot_qty=%s lqt_count=%s lqts=%s", + sale.id, sale_line.id, sale_line.lots[0].id, + sale_line.lots[0].lot_quantity, len(lqt), + [ + { + 'id': q.id, + 'lot_p': q.lot_p.id if q.lot_p else None, + 'lot_s': q.lot_s.id if q.lot_s else None, + 'quantity': q.lot_quantity, + 'unit': q.lot_unit.id if q.lot_unit else None, + 'shipment_in': ( + q.lot_shipment_in.id + if q.lot_shipment_in else None), + 'shipment_origin': q.lot_shipment_origin, + 'status': q.lot_status, + 'availability': q.lot_av, + } + for q in lqt + ]) + if lqt: + lqt[0].lot_shipment_in = self + LotQt.save(lqt) logger.info("SALE_LINKED_TO_SHIPMENT:%s",self) ContractStart = Pool().get('contracts.start') @@ -2524,11 +2625,11 @@ class ShipmentIn(metaclass=PoolMeta): ct = ContractStart() d = ContractDetail() ct.type = 'Purchase' - ct.matched = True - ct.shipment_in = self - ct.lot = sale_line.lots[0] - ct.product = sale_line.product - ct.unit = sale_line.unit + ct.matched = True + ct.shipment_in = self + ct.lot = sale_line.lots[0] + ct.product = sale_line.product + ct.unit = sale_line.unit d.party = Party.getPartyByName('FAIRCOT') if sale_line.enable_linked_currency: d.currency_unit = str(sale_line.linked_currency.id) + '_' + str(sale_line.linked_unit.id) @@ -2548,19 +2649,51 @@ class ShipmentIn(metaclass=PoolMeta): d.del_period = None d.from_del = None d.to_del = None - d.payment_term = sale.payment_term - ct.contracts = [d] - ContractFactory.create_contracts( - ct.contracts, - type_=ct.type, + d.payment_term = sale.payment_term + ct.contracts = [d] + logger.info( + "FINTRADE_CREATE_PURCHASE_FROM_SALE shipment=%s " + "declaration=%s chunk=%s sale=%s sale_line=%s " + "source_lot=%s source_lot_qty=%s requested=%s " + "ct_quantity=%s active_ids=%s", + getattr(self, 'id', None), dec_key, chunk_key, + sale.id, sale_line.id, ct.lot.id, + ct.lot.lot_quantity, d.quantity, + getattr(ct, 'quantity', None), + Transaction().context.get('active_ids')) + ContractFactory.create_contracts( + ct.contracts, + type_=ct.type, ct=ct, ) #Lots creation - vlot = sale_line.lots[0] - lqt = LotQt.search([('lot_s','=',vlot.id),('lot_p','>',0)]) - if lqt and vlot.lot_quantity > 0: - lqt = lqt[0] + vlot = sale_line.lots[0] + lqt = LotQt.search([('lot_s','=',vlot.id),('lot_p','>',0)]) + logger.info( + "FINTRADE_PHYSICAL_LOT_SOURCE shipment=%s declaration=%s " + "chunk=%s vlot=%s vlot_qty=%s source_lqt_count=%s " + "source_lqts=%s", + getattr(self, 'id', None), dec_key, chunk_key, vlot.id, + vlot.lot_quantity, len(lqt), + [ + { + 'id': q.id, + 'lot_p': q.lot_p.id if q.lot_p else None, + 'lot_s': q.lot_s.id if q.lot_s else None, + 'quantity': q.lot_quantity, + 'unit': q.lot_unit.id if q.lot_unit else None, + 'shipment_in': ( + q.lot_shipment_in.id + if q.lot_shipment_in else None), + 'shipment_origin': q.lot_shipment_origin, + 'status': q.lot_status, + 'availability': q.lot_av, + } + for q in lqt + ]) + if lqt and vlot.lot_quantity > 0: + lqt = lqt[0] l = LotAdd() l.lot_qt = lot_bales l.lot_unit = Uom.get_by_name('bale') @@ -2568,10 +2701,32 @@ class ShipmentIn(metaclass=PoolMeta): l.lot_quantity = round(lot_net_weight,2) l.lot_gross_quantity = round(lot_gross_weight,2) l.lot_premium = premium - l.lot_chunk_key = int(chunk_key) - logger.info("ADD_LOT:%s",int(chunk_key)) - LotQt.add_physical_lots(lqt,[l]) - + l.lot_chunk_key = int(chunk_key) + logger.info( + "FINTRADE_ADD_PHYSICAL_LOT shipment=%s " + "declaration=%s chunk=%s source_lqt=%s " + "source_open_before=%s add_net=%s add_gross=%s " + "add_bales=%s", + getattr(self, 'id', None), dec_key, chunk_key, + lqt.id, lqt.lot_quantity, l.lot_quantity, + l.lot_gross_quantity, l.lot_qt) + logger.info("ADD_LOT:%s",int(chunk_key)) + LotQt.add_physical_lots(lqt,[l]) + logger.info( + "FINTRADE_ADD_PHYSICAL_LOT_DONE shipment=%s " + "declaration=%s chunk=%s source_lqt=%s", + getattr(self, 'id', None), dec_key, chunk_key, + lqt.id) + else: + logger.info( + "FINTRADE_SKIP_PHYSICAL_LOT shipment=%s " + "declaration=%s chunk=%s reason=%s vlot_qty=%s " + "source_lqt_count=%s", + getattr(self, 'id', None), dec_key, chunk_key, + 'no matched purchase lqt' + if not lqt else 'vlot quantity not positive', + vlot.lot_quantity, len(lqt)) + return inv_date,inv_nb def html_to_text(self,html_content):