Bug Create contract
This commit is contained in:
@@ -392,6 +392,18 @@ them with Python guards and SQL diagnostics.
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;"><code>Lot.validate</code> creates the open <code>lot.qt</code> through <code>createVirtualPart</code>.
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">If the line is <code>created_by_code</code>:
|
||||
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
||||
<li style="margin:0.38rem 0;">it comes from a business workflow (<code>Create contracts</code>, matching, etc.);
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;"><code>Lot.validate</code> does not create an automatic open <code>lot.qt</code>;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">the workflow attaches or splits its source <code>lot.qt</code>;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">the blocking check applies only after the workflow is stable.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
### Updating `quantity_theorical`
|
||||
@@ -615,6 +627,18 @@ them with Python guards and SQL diagnostics.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">Trigger rule:
|
||||
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
||||
<li style="margin:0.38rem 0;">block inconsistent final states;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">do not block internal transient workflow states;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">use <code>Lot.skip_quantity_consistency()</code> only around a sequence that restores the invariants afterwards;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">call an explicit final check after the sequence.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">Non-zero orphan <code>lot.qt</code> block:
|
||||
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
||||
<li style="margin:0.38rem 0;"><code>lot.qt.validate</code>
|
||||
@@ -631,10 +655,20 @@ them with Python guards and SQL diagnostics.
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">shipping / unshipping;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">matched <code>Create contracts</code>;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">weighing.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">The <code>created_by_code</code> case is intentionally excluded from the immediate <code>Lot.validate</code> check:
|
||||
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
||||
<li style="margin:0.38rem 0;">the virtual lot is saved before the matched <code>lot.qt</code> is attached;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">the invariant is checked by the final workflow guard.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
### SQL Diagnostic
|
||||
|
||||
@@ -391,6 +391,18 @@ puis les sécuriser par des checks Python et des diagnostics SQL.
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;"><code>Lot.validate</code> crée le <code>lot.qt</code> ouvert via <code>createVirtualPart</code>.
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">Si la ligne est <code>created_by_code</code> :
|
||||
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
||||
<li style="margin:0.38rem 0;">elle provient d'un workflow métier (<code>Create contracts</code>, matching, etc.) ;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;"><code>Lot.validate</code> ne crée pas de <code>lot.qt</code> ouvert automatique ;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">le workflow rattache ou subdivise son <code>lot.qt</code> source ;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">le check bloquant s'applique seulement après stabilisation du workflow.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
### Modification de `quantity_theorical`
|
||||
@@ -614,6 +626,18 @@ puis les sécuriser par des checks Python et des diagnostics SQL.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">Règle de déclenchement :
|
||||
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
||||
<li style="margin:0.38rem 0;">bloquer les états finaux incohérents ;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">ne pas bloquer les états transitoires internes d'un workflow ;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">utiliser <code>Lot.skip_quantity_consistency()</code> uniquement autour d'une séquence qui rétablit ensuite les invariants ;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">appeler un check final explicite après la séquence.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">Blocage <code>lot.qt</code> orphelin non zéro :
|
||||
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
||||
<li style="margin:0.38rem 0;"><code>lot.qt.validate</code>
|
||||
@@ -630,10 +654,20 @@ puis les sécuriser par des checks Python et des diagnostics SQL.
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">shipping / unshipping ;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;"><code>Create contracts</code> en mode matched ;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">weighing.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">Le cas <code>created_by_code</code> est volontairement exclu du check immédiat <code>Lot.validate</code> :
|
||||
<ul style="margin:0.65rem 0 1rem 1.35rem; padding-left:1rem; list-style-type:circle;">
|
||||
<li style="margin:0.38rem 0;">le lot virtuel est sauvegardé avant que le <code>lot.qt</code> matched soit rattaché ;
|
||||
</li>
|
||||
<li style="margin:0.38rem 0;">l'invariant est contrôlé par le check final du workflow.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
### Diagnostic SQL
|
||||
|
||||
@@ -161,6 +161,11 @@ quantity_theorical
|
||||
- create one `virtual` lot.
|
||||
- The virtual lot receives a first `lot.qt.hist` entry.
|
||||
- `Lot.validate` creates the open `lot.qt` through `createVirtualPart`.
|
||||
- If the line is `created_by_code`:
|
||||
- it comes from a business workflow (`Create contracts`, matching, etc.);
|
||||
- `Lot.validate` does not create an automatic open `lot.qt`;
|
||||
- the workflow attaches or splits its source `lot.qt`;
|
||||
- the blocking check applies only after the workflow is stable.
|
||||
|
||||
### Updating `quantity_theorical`
|
||||
|
||||
@@ -266,6 +271,11 @@ free_quantity = target_quantity - sum(already matched or shipped lot.qt)
|
||||
|
||||
- Central check:
|
||||
- `lot.lot.assert_lines_quantity_consistency()`
|
||||
- Trigger rule:
|
||||
- block inconsistent final states;
|
||||
- do not block internal transient workflow states;
|
||||
- use `Lot.skip_quantity_consistency()` only around a sequence that restores the invariants afterwards;
|
||||
- call an explicit final check after the sequence.
|
||||
- Non-zero orphan `lot.qt` block:
|
||||
- `lot.qt.validate`
|
||||
- Called after:
|
||||
@@ -273,7 +283,11 @@ free_quantity = target_quantity - sum(already matched or shipped lot.qt)
|
||||
- physical lot creation / deletion;
|
||||
- matching / unmatching;
|
||||
- shipping / unshipping;
|
||||
- matched `Create contracts`;
|
||||
- weighing.
|
||||
- The `created_by_code` case is intentionally excluded from the immediate `Lot.validate` check:
|
||||
- the virtual lot is saved before the matched `lot.qt` is attached;
|
||||
- the invariant is checked by the final workflow guard.
|
||||
|
||||
### SQL Diagnostic
|
||||
|
||||
|
||||
@@ -160,6 +160,11 @@ quantity_theorical
|
||||
- création d'un lot `virtual`.
|
||||
- Le lot virtuel reçoit une première entrée `lot.qt.hist`.
|
||||
- `Lot.validate` crée le `lot.qt` ouvert via `createVirtualPart`.
|
||||
- Si la ligne est `created_by_code` :
|
||||
- elle provient d'un workflow métier (`Create contracts`, matching, etc.) ;
|
||||
- `Lot.validate` ne crée pas de `lot.qt` ouvert automatique ;
|
||||
- le workflow rattache ou subdivise son `lot.qt` source ;
|
||||
- le check bloquant s'applique seulement après stabilisation du workflow.
|
||||
|
||||
### Modification de `quantity_theorical`
|
||||
|
||||
@@ -264,6 +269,11 @@ free_quantity = target_quantity - somme(lot.qt déjà matchés ou shippés)
|
||||
|
||||
- Check central :
|
||||
- `lot.lot.assert_lines_quantity_consistency()`
|
||||
- Règle de déclenchement :
|
||||
- bloquer les états finaux incohérents ;
|
||||
- ne pas bloquer les états transitoires internes d'un workflow ;
|
||||
- utiliser `Lot.skip_quantity_consistency()` uniquement autour d'une séquence qui rétablit ensuite les invariants ;
|
||||
- appeler un check final explicite après la séquence.
|
||||
- Blocage `lot.qt` orphelin non zéro :
|
||||
- `lot.qt.validate`
|
||||
- Appels après :
|
||||
@@ -271,7 +281,11 @@ free_quantity = target_quantity - somme(lot.qt déjà matchés ou shippés)
|
||||
- création / suppression de lots physiques ;
|
||||
- matching / unmatching ;
|
||||
- shipping / unshipping ;
|
||||
- `Create contracts` en mode matched ;
|
||||
- weighing.
|
||||
- Le cas `created_by_code` est volontairement exclu du check immédiat `Lot.validate` :
|
||||
- le lot virtuel est sauvegardé avant que le `lot.qt` matched soit rattaché ;
|
||||
- l'invariant est contrôlé par le check final du workflow.
|
||||
|
||||
### Diagnostic SQL
|
||||
|
||||
|
||||
@@ -880,6 +880,9 @@ class Lot(metaclass=PoolMeta):
|
||||
lqt = LotQt.search([('lot_p','=',lot.id)])
|
||||
if len(lqt)==0 and not lot.line.created_by_code:
|
||||
lot.createVirtualPart(lot.lot_quantity,None,None)
|
||||
# Wizard-created matched lines attach/split lot.qt after
|
||||
# the virtual lot save; the final workflow check covers
|
||||
# the complete state.
|
||||
if not lot.line.created_by_code:
|
||||
virtual_lots_to_check.append(lot)
|
||||
|
||||
@@ -887,6 +890,8 @@ class Lot(metaclass=PoolMeta):
|
||||
lqt = LotQt.search([('lot_s','=',lot.id)])
|
||||
if len(lqt)==0 and not lot.sale_line.created_by_code:
|
||||
lot.createVirtualPart(lot.lot_quantity,None,lot.id,'only sale')
|
||||
# Same rule as purchase side: do not check the transient
|
||||
# save of a virtual lot created by matching code.
|
||||
if not lot.sale_line.created_by_code:
|
||||
virtual_lots_to_check.append(lot)
|
||||
|
||||
|
||||
@@ -331,30 +331,8 @@ class ContractFactory:
|
||||
lqh.gross_quantity = round(lot.lot_quantity, 5)
|
||||
lot.lot_hist = [lqh]
|
||||
|
||||
logger.info(
|
||||
"CREATE_CONTRACT_LOT_BEFORE_SAVE type=%s line=%s qty=%s "
|
||||
"source_lot=%s source_lqt=%s shipment=%s skip_before=%s",
|
||||
type_,
|
||||
getattr(line, 'id', None),
|
||||
getattr(lot, 'lot_quantity', None),
|
||||
getattr(source.get('lot'), 'id', None),
|
||||
getattr(source.get('lqt'), 'id', None),
|
||||
source.get('shipment_origin'),
|
||||
Transaction().context.get(
|
||||
'_purchase_trade_skip_quantity_consistency'))
|
||||
|
||||
with Lot.skip_quantity_consistency():
|
||||
lot.save()
|
||||
logger.info(
|
||||
"CREATE_CONTRACT_LOT_SAVED type=%s line=%s lot=%s qty=%s "
|
||||
"source_lot=%s source_lqt=%s shipment=%s",
|
||||
type_,
|
||||
getattr(line, 'id', None),
|
||||
getattr(lot, 'id', None),
|
||||
getattr(lot, 'lot_quantity', None),
|
||||
getattr(source.get('lot'), 'id', None),
|
||||
getattr(source.get('lqt'), 'id', None),
|
||||
source.get('shipment_origin'))
|
||||
|
||||
vlot = source['lot']
|
||||
shipment_origin = source.get('shipment_origin')
|
||||
@@ -363,14 +341,6 @@ class ContractFactory:
|
||||
if type_ == 'Purchase':
|
||||
if cls._attach_or_split_source_lot_qt(source, lot, type_):
|
||||
return
|
||||
logger.info(
|
||||
"CREATE_CONTRACT_LOT_FALLBACK type=%s new_lot=%s "
|
||||
"source_lot=%s qty=%s shipment=%s",
|
||||
type_,
|
||||
getattr(lot, 'id', None),
|
||||
getattr(vlot, 'id', None),
|
||||
qt,
|
||||
shipment_origin)
|
||||
if not lot.updateVirtualPart(qt, shipment_origin, vlot):
|
||||
lot.createVirtualPart(qt, shipment_origin, vlot)
|
||||
|
||||
@@ -379,14 +349,6 @@ class ContractFactory:
|
||||
else:
|
||||
if cls._attach_or_split_source_lot_qt(source, lot, type_):
|
||||
return
|
||||
logger.info(
|
||||
"CREATE_CONTRACT_LOT_FALLBACK type=%s new_lot=%s "
|
||||
"source_lot=%s qty=%s shipment=%s",
|
||||
type_,
|
||||
getattr(lot, 'id', None),
|
||||
getattr(vlot, 'id', None),
|
||||
qt,
|
||||
shipment_origin)
|
||||
if not vlot.updateVirtualPart(qt, shipment_origin, lot):
|
||||
vlot.createVirtualPart(qt, shipment_origin, lot)
|
||||
|
||||
@@ -404,35 +366,12 @@ class ContractFactory:
|
||||
if not source_lqt:
|
||||
source_lqt = cls._find_source_lot_qt(source, type_)
|
||||
if not source_lqt:
|
||||
logger.info(
|
||||
"CREATE_CONTRACT_ATTACH_SKIP no source_lqt type=%s "
|
||||
"new_lot=%s source_lot=%s qty=%s shipment=%s",
|
||||
type_,
|
||||
getattr(new_virtual_lot, 'id', None),
|
||||
getattr(source.get('lot'), 'id', None),
|
||||
quantity,
|
||||
source.get('shipment_origin'))
|
||||
return False
|
||||
|
||||
remaining = cls._normalize_quantity(source_lqt.lot_quantity) - quantity
|
||||
if remaining < 0:
|
||||
logger.info(
|
||||
"CREATE_CONTRACT_ATTACH_SKIP insufficient source_lqt=%s "
|
||||
"available=%s qty=%s",
|
||||
getattr(source_lqt, 'id', None),
|
||||
getattr(source_lqt, 'lot_quantity', None),
|
||||
quantity)
|
||||
return False
|
||||
|
||||
logger.info(
|
||||
"CREATE_CONTRACT_ATTACH type=%s source_lqt=%s new_lot=%s "
|
||||
"source_lot=%s qty=%s remaining=%s",
|
||||
type_,
|
||||
getattr(source_lqt, 'id', None),
|
||||
getattr(new_virtual_lot, 'id', None),
|
||||
getattr(source.get('lot'), 'id', None),
|
||||
quantity,
|
||||
remaining)
|
||||
if remaining:
|
||||
matched_lqt = LotQt()
|
||||
matched_lqt.lot_p = (
|
||||
@@ -495,22 +434,6 @@ class ContractFactory:
|
||||
for lqt in lqts:
|
||||
if cls._normalize_quantity(lqt.lot_quantity) >= quantity:
|
||||
return lqt
|
||||
logger.info(
|
||||
"CREATE_CONTRACT_FIND_SOURCE_LQT_EMPTY type=%s lot=%s qty=%s "
|
||||
"shipment=%s candidates=%s",
|
||||
type_,
|
||||
getattr(lot, 'id', None),
|
||||
quantity,
|
||||
source.get('shipment_origin'),
|
||||
[
|
||||
(
|
||||
getattr(lqt, 'id', None),
|
||||
getattr(lqt, 'lot_quantity', None),
|
||||
getattr(getattr(lqt, 'lot_p', None), 'id', None),
|
||||
getattr(getattr(lqt, 'lot_s', None), 'id', None),
|
||||
)
|
||||
for lqt in lqts
|
||||
])
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user