Faircot cron

This commit is contained in:
2026-07-19 21:59:22 +02:00
parent a94f36e26f
commit bd72f8cef3
3 changed files with 204 additions and 99 deletions

View File

@@ -348,15 +348,23 @@ class AutomationCron(ModelSQL, ModelView):
with Transaction().set_context( with Transaction().set_context(
_purchase_trade_skip_physical_lot_tolerance_warning=True): _purchase_trade_skip_physical_lot_tolerance_warning=True):
inv_date, inv_nb = shipment._create_lots_from_fintrade() inv_date, inv_nb = shipment._create_lots_from_fintrade()
shipment.controller = shipment.get_controller() shipment = ShipmentIn(shipment.id)
shipment.controller_target = controller if shipment.incoming_moves:
shipment.create_fee(shipment.controller) shipment.controller = shipment.get_controller()
shipment.instructions = shipment.get_instructions_html( shipment.controller_target = controller
inv_date, inv_nb) shipment.create_fee(shipment.controller)
ShipmentIn.save([shipment]) shipment.instructions = shipment.get_instructions_html(
inv_date, inv_nb)
ShipmentIn.save([shipment])
else:
logger.info(
"Shipment %s cree sans lots : fees/instructions "
"non crees, voir FINTRADE_CLEANUP_NEEDED",
si_number)
trans_shipment.commit() trans_shipment.commit()
successful_shipments += 1 if shipment.incoming_moves:
logger.info(f"Shipment {si_number} cree avec succes") successful_shipments += 1
logger.info(f"Shipment {si_number} cree avec succes")
except Exception as e: except Exception as e:
error_details = { error_details = {

View File

@@ -1014,9 +1014,8 @@ class Lot(metaclass=PoolMeta):
else: else:
lqt.lot_s = lot_s lqt.lot_s = lot_s
lqt.lot_p = None lqt.lot_p = None
if sh: if sh:
logger.info("IDSHIP:%s",sh) str_sh = str(sh)
str_sh = str(sh)
index = str_sh.rfind(",") index = str_sh.rfind(",")
if index != -1: if index != -1:
id = int(str_sh[index+1:].strip()) id = int(str_sh[index+1:].strip())
@@ -1059,10 +1058,8 @@ class Lot(metaclass=PoolMeta):
lqts = LotQt.search([('lot_p','=',vlot),('lot_shipment_out','=',id),('lot_s','=',lot_s)]) lqts = LotQt.search([('lot_p','=',vlot),('lot_shipment_out','=',id),('lot_s','=',lot_s)])
else: else:
lqts = LotQt.search([('lot_p','=',vlot),('lot_s','=',lot_s),('lot_shipment_in','=',None),('lot_shipment_internal','=',None),('lot_shipment_out','=',None)]) lqts = LotQt.search([('lot_p','=',vlot),('lot_s','=',lot_s),('lot_shipment_in','=',None),('lot_shipment_internal','=',None),('lot_shipment_out','=',None)])
if lqts: if lqts:
logger.info("UVP_QT:%s",qt) lqts[0].lot_quantity += qt
logger.info("UVP_LQ:%s",lqts[0].lot_quantity)
lqts[0].lot_quantity += qt
if lqts[0].lot_quantity < 0: if lqts[0].lot_quantity < 0:
lqts[0].lot_quantity = 0 lqts[0].lot_quantity = 0
LotQt.save(lqts) LotQt.save(lqts)

View File

@@ -2427,14 +2427,92 @@ class ShipmentIn(metaclass=PoolMeta):
"FINTRADE_LOTS_START shipment=%s reference=%s bl=%s rows=%s", "FINTRADE_LOTS_START shipment=%s reference=%s bl=%s rows=%s",
getattr(self, 'id', None), self.reference, self.bl_number, getattr(self, 'id', None), self.reference, self.bl_number,
len(rows)) len(rows))
logger.info("ROWS:%s",rows)
inv_date = None inv_date = None
inv_nb = None inv_nb = None
cleanup_needed = []
skipped_rows = 0
def normalize_quantity(value):
return Decimal(str(value or 0)).quantize(Decimal('0.00001'))
def lot_summary(lot):
return {
'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),
}
def lqt_summary(lqt):
return {
'id': lqt.id,
'lot_p': lqt.lot_p.id if lqt.lot_p else None,
'lot_s': lqt.lot_s.id if lqt.lot_s else None,
'quantity': lqt.lot_quantity,
'unit': lqt.lot_unit.id if lqt.lot_unit else None,
'shipment_in': (
lqt.lot_shipment_in.id
if lqt.lot_shipment_in else None),
'shipment_origin': lqt.lot_shipment_origin,
'status': lqt.lot_status,
'availability': lqt.lot_av,
}
def add_cleanup(reason, declaration, chunk, lot_number, quantity,
details=None):
item = {
'reason': reason,
'shipment': getattr(self, 'id', None),
'reference': self.reference,
'bl': self.bl_number,
'declaration': declaration,
'chunk': chunk,
'lot_number': lot_number,
'quantity': quantity,
'details': details or {},
}
cleanup_needed.append(item)
logger.info("FINTRADE_CLEANUP_NEEDED %s", item)
def select_or_link_lqt(lqts, quantity):
quantity = normalize_quantity(quantity)
current = [
q for q in lqts
if q.lot_p
and q.lot_shipment_in
and q.lot_shipment_in.id == self.id
and normalize_quantity(q.lot_quantity) >= quantity
]
if current:
return current[0]
unshipped = [
q for q in lqts
if q.lot_p
and not q.lot_shipment_in
and not q.lot_shipment_internal
and not q.lot_shipment_out
and normalize_quantity(q.lot_quantity) >= quantity
]
if not unshipped:
return None
return LotQt.link_to_transport(
unshipped[0], quantity,
'stock.shipment.in,%s' % self.id)
if rows: if rows:
sale_line = None sale_line = None
for row in rows: for row in rows:
logger.info("ROW:%s",row) #Purchase & Sale creation
#Purchase & Sale creation
LotQt = Pool().get('lot.qt') LotQt = Pool().get('lot.qt')
Lot = Pool().get('lot.lot') Lot = Pool().get('lot.lot')
LotAdd = Pool().get('lot.add.line') LotAdd = Pool().get('lot.add.line')
@@ -2457,10 +2535,9 @@ class ShipmentIn(metaclass=PoolMeta):
getattr(self, 'id', None), self.reference, getattr(self, 'id', None), self.reference,
dec_key, chunk_key) dec_key, chunk_key)
lot_unit = str(row[5]).strip().lower() lot_unit = str(row[5]).strip().lower()
product = str(row[6]).strip().upper() product = str(row[6]).strip().upper()
lot_net_weight = Decimal(row[4]) lot_net_weight = Decimal(row[4])
logger.info("LOT_NET_WEIGHT:%s",lot_net_weight) lot_gross_weight = Decimal(row[3])
lot_gross_weight = Decimal(row[3])
lot_bales = Decimal(row[2]) lot_bales = Decimal(row[2])
lot_number = row[1] lot_number = row[1]
customer = str(row[7]).strip().upper() customer = str(row[7]).strip().upper()
@@ -2480,33 +2557,49 @@ class ShipmentIn(metaclass=PoolMeta):
chunk_key, dec_key, lot_net_weight, lot_gross_weight, chunk_key, dec_key, lot_net_weight, lot_gross_weight,
lot_bales, lot_unit, sell_price_currency, lot_bales, lot_unit, sell_price_currency,
sell_price_unit, customer, reference) sell_price_unit, customer, reference)
if chunk_key_int is None:
skipped_rows += 1
add_cleanup(
'invalid_chunk_key', dec_key, chunk_key, lot_number,
lot_net_weight)
continue
if chunk_key_int is not None: if chunk_key_int is not None:
existing_chunk_lots = Lot.search([ existing_chunk_lots = Lot.search([
('lot_chunk_key', '=', chunk_key_int), ('lot_chunk_key', '=', chunk_key_int),
]) ])
if existing_chunk_lots:
duplicate_details = {
'existing_count': len(existing_chunk_lots),
'existing_sample': [
lot_summary(lot)
for lot in existing_chunk_lots[:20]
],
'existing_shipments': sorted({
lot.lot_shipment_in.id
for lot in existing_chunk_lots
if getattr(lot, 'lot_shipment_in', None)
}),
}
skipped_rows += 1
add_cleanup(
'chunk_already_has_physical_lots',
dec_key, chunk_key, lot_number,
lot_net_weight, duplicate_details)
logger.info(
"FINTRADE_SKIP_DUPLICATE_CHUNK shipment=%s "
"reference=%s chunk=%s existing_count=%s "
"existing_shipments=%s",
getattr(self, 'id', None), self.reference,
chunk_key, duplicate_details['existing_count'],
duplicate_details['existing_shipments'])
continue
logger.info( logger.info(
"FINTRADE_CHUNK_CHECK chunk=%s existing_lots=%s", "FINTRADE_CHUNK_CHECK chunk=%s existing_count=%s "
"existing_sample=%s",
chunk_key, chunk_key,
[ len(existing_chunk_lots),
{ [lot_summary(lot) for lot in existing_chunk_lots[:20]])
'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]) declaration = SaleLine.search(['note','=',dec_key])
logger.info( logger.info(
"FINTRADE_DECLARATION_SEARCH declaration=%s count=%s " "FINTRADE_DECLARATION_SEARCH declaration=%s count=%s "
@@ -2522,38 +2615,41 @@ class ShipmentIn(metaclass=PoolMeta):
sale_line.sale.id if sale_line.sale else None, sale_line.sale.id if sale_line.sale else None,
sale_line.quantity, sale_line.quantity_theorical, sale_line.quantity, sale_line.quantity_theorical,
[lot.id for lot in sale_line.lots]) [lot.id for lot in sale_line.lots])
if not sale_line.lots:
skipped_rows += 1
add_cleanup(
'declaration_without_virtual_lot',
dec_key, chunk_key, lot_number, lot_net_weight,
{'sale_line': sale_line.id})
continue
vlot = sale_line.lots[0] vlot = sale_line.lots[0]
lqt = LotQt.search([('lot_s','=',vlot.id)]) lqt = LotQt.search([('lot_s','=',vlot.id)])
logger.info( logger.info(
"FINTRADE_DECLARATION_LQT declaration=%s vlot=%s " "FINTRADE_DECLARATION_LQT declaration=%s vlot=%s "
"vlot_qty=%s lqt_count=%s lqts=%s", "vlot_qty=%s lqt_count=%s lqts=%s",
dec_key, vlot.id, vlot.lot_quantity, len(lqt), dec_key, vlot.id, vlot.lot_quantity, len(lqt),
[ [lqt_summary(q) for q in lqt])
lqt = select_or_link_lqt(lqt, lot_net_weight)
if not lqt:
skipped_rows += 1
add_cleanup(
'no_open_matched_lot_qt_for_declaration',
dec_key, chunk_key, lot_number, lot_net_weight,
{ {
'id': q.id, 'sale_line': sale_line.id,
'lot_p': q.lot_p.id if q.lot_p else None, 'vlot': vlot.id,
'lot_s': q.lot_s.id if q.lot_s else None, 'available_lqts': [
'quantity': q.lot_quantity, lqt_summary(q)
'unit': q.lot_unit.id if q.lot_unit else None, for q in LotQt.search([
'shipment_in': ( ('lot_s', '=', vlot.id),
q.lot_shipment_in.id ('lot_p', '>', 0),
if q.lot_shipment_in else None), ])
'shipment_origin': q.lot_shipment_origin, ],
'status': q.lot_status, })
'availability': q.lot_av, continue
} sale_line.quantity_theorical += round(lot_net_weight,2)
for q in lqt SaleLine.save([sale_line])
]) else:
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])
lq.lot_p.updateVirtualPart(round(lot_net_weight,2),self,lq.lot_s)
vlot.set_current_quantity(round(lot_net_weight,2),round(lot_gross_weight,2),1)
Lot.save([vlot])
else:
sale = Sale() sale = Sale()
sale_line = SaleLine() sale_line = SaleLine()
sale.party = Party.getPartyByName(customer,'CLIENT') sale.party = Party.getPartyByName(customer,'CLIENT')
@@ -2628,6 +2724,7 @@ class ShipmentIn(metaclass=PoolMeta):
ct.matched = True ct.matched = True
ct.shipment_in = self ct.shipment_in = self
ct.lot = sale_line.lots[0] ct.lot = sale_line.lots[0]
ct.quantity = sale_line.quantity
ct.product = sale_line.product ct.product = sale_line.product
ct.unit = sale_line.unit ct.unit = sale_line.unit
d.party = Party.getPartyByName('FAIRCOT') d.party = Party.getPartyByName('FAIRCOT')
@@ -2664,39 +2761,28 @@ class ShipmentIn(metaclass=PoolMeta):
ContractFactory.create_contracts( ContractFactory.create_contracts(
ct.contracts, ct.contracts,
type_=ct.type, type_=ct.type,
ct=ct, ct=ct,
) )
#Lots creation #Lots creation
vlot = sale_line.lots[0] vlot = sale_line.lots[0]
lqt = LotQt.search([('lot_s','=',vlot.id),('lot_p','>',0)]) lqts = LotQt.search([
('lot_s', '=', vlot.id),
('lot_p', '>', 0),
])
if not declaration:
lqt = select_or_link_lqt(lqts, lot_net_weight)
logger.info( logger.info(
"FINTRADE_PHYSICAL_LOT_SOURCE shipment=%s declaration=%s " "FINTRADE_PHYSICAL_LOT_SOURCE shipment=%s declaration=%s "
"chunk=%s vlot=%s vlot_qty=%s source_lqt_count=%s " "chunk=%s vlot=%s vlot_qty=%s source_lqt_count=%s "
"source_lqts=%s", "source_lqts=%s",
getattr(self, 'id', None), dec_key, chunk_key, vlot.id, getattr(self, 'id', None), dec_key, chunk_key, vlot.id,
vlot.lot_quantity, len(lqt), vlot.lot_quantity, len(lqts),
[ [lqt_summary(q) for q in lqts])
{ if lqt and normalize_quantity(lqt.lot_quantity) >= normalize_quantity(lot_net_weight):
'id': q.id, l = LotAdd()
'lot_p': q.lot_p.id if q.lot_p else None, l.lot_qt = lot_bales
'lot_s': q.lot_s.id if q.lot_s else None, l.lot_unit = Uom.get_by_name('bale')
'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')
l.lot_unit_line = Uom.get_by_name(lot_unit) l.lot_unit_line = Uom.get_by_name(lot_unit)
l.lot_quantity = round(lot_net_weight,2) l.lot_quantity = round(lot_net_weight,2)
l.lot_gross_quantity = round(lot_gross_weight,2) l.lot_gross_quantity = round(lot_gross_weight,2)
@@ -2710,7 +2796,6 @@ class ShipmentIn(metaclass=PoolMeta):
getattr(self, 'id', None), dec_key, chunk_key, getattr(self, 'id', None), dec_key, chunk_key,
lqt.id, lqt.lot_quantity, l.lot_quantity, lqt.id, lqt.lot_quantity, l.lot_quantity,
l.lot_gross_quantity, l.lot_qt) l.lot_gross_quantity, l.lot_qt)
logger.info("ADD_LOT:%s",int(chunk_key))
LotQt.add_physical_lots(lqt,[l]) LotQt.add_physical_lots(lqt,[l])
logger.info( logger.info(
"FINTRADE_ADD_PHYSICAL_LOT_DONE shipment=%s " "FINTRADE_ADD_PHYSICAL_LOT_DONE shipment=%s "
@@ -2718,14 +2803,29 @@ class ShipmentIn(metaclass=PoolMeta):
getattr(self, 'id', None), dec_key, chunk_key, getattr(self, 'id', None), dec_key, chunk_key,
lqt.id) lqt.id)
else: else:
skipped_rows += 1
reason = 'no matched purchase lqt'
if lqt:
reason = 'matched purchase lqt open quantity too low'
add_cleanup(
reason, dec_key, chunk_key, lot_number, lot_net_weight,
{
'selected_lqt': lqt_summary(lqt) if lqt else None,
'source_lqts': [lqt_summary(q) for q in lqts],
})
logger.info( logger.info(
"FINTRADE_SKIP_PHYSICAL_LOT shipment=%s " "FINTRADE_SKIP_PHYSICAL_LOT shipment=%s "
"declaration=%s chunk=%s reason=%s vlot_qty=%s " "declaration=%s chunk=%s reason=%s vlot_qty=%s "
"source_lqt_count=%s", "source_lqt_count=%s",
getattr(self, 'id', None), dec_key, chunk_key, getattr(self, 'id', None), dec_key, chunk_key,
'no matched purchase lqt' reason, vlot.lot_quantity, len(lqts))
if not lqt else 'vlot quantity not positive',
vlot.lot_quantity, len(lqt)) if cleanup_needed:
logger.info(
"FINTRADE_CLEANUP_SUMMARY shipment=%s reference=%s "
"skipped_rows=%s cleanup_count=%s cleanup_items=%s",
getattr(self, 'id', None), self.reference, skipped_rows,
len(cleanup_needed), cleanup_needed)
return inv_date,inv_nb return inv_date,inv_nb