Lots Management

This commit is contained in:
2026-05-09 16:27:29 +02:00
parent d8ac25dcda
commit 65fe45bae2
4 changed files with 130 additions and 21 deletions

View File

@@ -702,6 +702,18 @@ Owner technique: `a completer`
`All`, `Purchase`, `Sale`.
- Le filtre `Shipping status` porte uniquement le lien `shipment_in` du
`lot.lot` ou du `lot.qt`.
- Les dates de contexte `As of` et `To` filtrent les contrats via
`purchase.purchase_date` et `sale.sale_date`.
- Si le filtre `Side` vaut `Purchase`, les bornes de dates s'appliquent au
`purchase_date`.
- Si le filtre `Side` vaut `Sale`, les bornes de dates s'appliquent au
`sale_date`.
- Si le filtre `Side` vaut `All`, une ligne est conservee si son
`purchase_date` ou son `sale_date` entre dans les bornes.
- Le filtre `Book` du rapport cible une valeur de dimension analytique
rattachee au contrat achat ou vente via `analytic.dimension.assignment`.
- Le filtre `Strategy` cible les strategies MTM rattachees aux lignes achat
ou vente (`purchase.strategy` / `sale.strategy`).
- Resultat attendu:
- `Unshipped`: aucun `shipment_in` lie au `lot.lot` ou au `lot.qt`.
- `Scheduled`: un `shipment_in` est lie et son etat est `draft`.

View File

@@ -9,10 +9,11 @@ from trytond.tools import (
from trytond.transaction import Transaction, inactive_records
from decimal import getcontext, Decimal, ROUND_HALF_UP
from sql.aggregate import Count, Max, Min, Sum, Avg, BoolOr
from sql.conditionals import Case
from sql import Column, Literal, Union, Select
from sql.conditionals import Coalesce
from sql.functions import CurrentTimestamp, DateTrunc, Abs
from sql.conditionals import Case
from sql import Column, Literal, Union, Select
from sql.conditionals import Coalesce
from sql.functions import CurrentTimestamp, DateTrunc, Abs
from sql.operators import Exists
from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction
from itertools import chain, groupby
from operator import itemgetter
@@ -1221,7 +1222,8 @@ class LotQt(
def getQuery(cls, purchase=None, sale=None, shipment=None, type=None,
state=None, qttype=None, supplier=None, client=None, ps=None,
lot_status=None, group=None, product=None, location=None,
origin=None, finished=False, shipping_status=None):
origin=None, finished=False, shipping_status=None, asof=None,
todate=None, book=None, strategy=None):
pool = Pool()
LotQt = pool.get('lot.qt')
lqt = LotQt.__table__()
@@ -1259,10 +1261,48 @@ class LotQt(
final_s = Final_s.__table__()
FinalLine_s = pool.get('account.invoice.line')
final_line_s = FinalLine_s.__table__()
ProductUom = pool.get('product.uom')
puom = ProductUom.__table__()
ProductUomLine = pool.get('product.uom')
puoml = ProductUomLine.__table__()
ProductUom = pool.get('product.uom')
puom = ProductUom.__table__()
ProductUomLine = pool.get('product.uom')
puoml = ProductUomLine.__table__()
PurchaseStrategy = pool.get('purchase.strategy')
pstr = PurchaseStrategy.__table__()
SaleStrategy = pool.get('sale.strategy')
sstr = SaleStrategy.__table__()
AnalyticDimensionAssignment = pool.get('analytic.dimension.assignment')
ada = AnalyticDimensionAssignment.__table__()
def date_filter(purchase_date, sale_date):
if not asof and not todate:
return None
purchase_clause = Literal(True)
sale_clause = Literal(True)
if asof:
purchase_clause &= (purchase_date >= asof)
sale_clause &= (sale_date >= asof)
if todate:
purchase_clause &= (purchase_date <= todate)
sale_clause &= (sale_date <= todate)
if ps == 'P':
return purchase_clause
elif ps == 'S':
return sale_clause
return purchase_clause | sale_clause
def book_filter(purchase_id, sale_id):
if not book:
return None
purchase_book = Exists(ada.select(
ada.id,
where=((ada.purchase == purchase_id) & (ada.value == book))))
sale_book = Exists(ada.select(
ada.id,
where=((ada.sale == sale_id) & (ada.value == book))))
if ps == 'P':
return purchase_book
elif ps == 'S':
return sale_book
return purchase_book | sale_book
wh = ((lqt.lot_quantity > 0) & ((lp.lot_type == 'virtual') | (ls.lot_type == 'virtual')))
@@ -1285,8 +1325,28 @@ class LotQt(
wh &= (lqt.lot_shipment_in == shipment)
if product:
wh &= (lqt.lot_product == product)
if location:
wh &= (Case((lqt.lot_shipment_in > 0, si.to_location),else_=Case((lqt.lot_shipment_internal > 0, sin.to_location),else_=pu.to_location)) == location)
if location:
wh &= (Case((lqt.lot_shipment_in > 0, si.to_location),else_=Case((lqt.lot_shipment_internal > 0, sin.to_location),else_=pu.to_location)) == location)
date_clause = date_filter(pu.purchase_date, sa.sale_date)
if date_clause is not None:
wh &= date_clause
book_clause = book_filter(pu.id, sa.id)
if book_clause is not None:
wh &= book_clause
if strategy:
purchase_strategy = Exists(pstr.select(
pstr.id,
where=((pstr.line == pl.id) & (pstr.strategy == strategy))))
sale_strategy = Exists(sstr.select(
sstr.id,
where=((sstr.sale_line == sl.id)
& (sstr.strategy == strategy))))
if ps == 'P':
wh &= purchase_strategy
elif ps == 'S':
wh &= sale_strategy
else:
wh &= (purchase_strategy | sale_strategy)
if type=='matched':
wh &= ((ls.sale_line > 0) & (lp.line > 0))
elif type=='not matched':
@@ -1403,10 +1463,30 @@ class LotQt(
wh2 &= (lp.lot_shipment_in == shipment)
if product:
wh2 &= (lp.lot_product == product)
if location:
wh2 &= (Case((lp.lot_shipment_in > 0, si.to_location), else_=Case((lp.lot_shipment_internal > 0, sin.to_location), else_=pu.to_location)) == location)
if type=='matched':
wh2 &= (lp.sale_line != None)
if location:
wh2 &= (Case((lp.lot_shipment_in > 0, si.to_location), else_=Case((lp.lot_shipment_internal > 0, sin.to_location), else_=pu.to_location)) == location)
date_clause = date_filter(pu.purchase_date, sa.sale_date)
if date_clause is not None:
wh2 &= date_clause
book_clause = book_filter(pu.id, sa.id)
if book_clause is not None:
wh2 &= book_clause
if strategy:
purchase_strategy = Exists(pstr.select(
pstr.id,
where=((pstr.line == pl.id) & (pstr.strategy == strategy))))
sale_strategy = Exists(sstr.select(
sstr.id,
where=((sstr.sale_line == sl.id)
& (sstr.strategy == strategy))))
if ps == 'P':
wh2 &= purchase_strategy
elif ps == 'S':
wh2 &= sale_strategy
else:
wh2 &= (purchase_strategy | sale_strategy)
if type=='matched':
wh2 &= (lp.sale_line != None)
elif type=='not matched':
wh2 &= (lp.sale_line == None)
if shipping_status == 'unshipped':
@@ -1935,8 +2015,10 @@ class LotReport(
client = context.get('client')
ps = context.get('ps')
shipping_status = context.get('shipping_status')
#asof = context.get('asof')
#todate = context.get('todate')
asof = context.get('asof')
todate = context.get('todate')
book = context.get('book')
strategy = context.get('strategy')
finished = context.get('finished')
query = LotQT.getQuery(
purchase=purchase,
@@ -1953,7 +2035,11 @@ class LotReport(
location=location,
origin=origin,
finished=finished,
shipping_status=shipping_status)
shipping_status=shipping_status,
asof=asof,
todate=todate,
book=book,
strategy=strategy)
return query
@classmethod
@@ -2004,6 +2090,8 @@ class LotContext(ModelView):
client = fields.Many2One('party.party', "Client")
sale = fields.Many2One('sale.sale', "Sale")
shipment = fields.Many2One('stock.shipment.in', "Shipment")
book = fields.Many2One('analytic.dimension.value', "Book")
strategy = fields.Many2One('mtm.strategy', "Strategy")
product = fields.Many2One('product.product',"Product")
location = fields.Many2One('stock.location',"Location")
type = fields.Selection([
@@ -2110,8 +2198,9 @@ class LotContext(ModelView):
fields_names = fields_names.copy() if fields_names is not None else []
for field_name in [
'purchase', 'sale', 'shipment', 'supplier', 'client',
'product', 'location', 'type', 'shipping_status', 'ps',
'state', 'wh', 'mode', 'group', 'origin', 'finished']:
'book', 'strategy', 'product', 'location', 'type',
'shipping_status', 'ps', 'state', 'wh', 'mode', 'group',
'origin', 'finished', 'asof', 'todate']:
if field_name not in fields_names:
fields_names.append(field_name)
return super().get_context(fields_names=fields_names)

View File

@@ -9,6 +9,10 @@
<field name="sale"/>
<label name="shipment"/>
<field name="shipment"/>
<label name="book"/>
<field name="book"/>
<label name="strategy"/>
<field name="strategy"/>
<label name="supplier"/>
<field name="supplier"/>
<label name="client"/>
@@ -23,12 +27,16 @@
<field name="shipping_status"/>
<label name="ps"/>
<field name="ps"/>
<!--
<label name="state"/>
<field name="state"/>
-->
<label name="wh"/>
<field name="wh"/>
<!--
<label name="mode"/>
<field name="mode"/>
-->
<label name="group"/>
<field name="group"/>
<label name="origin"/>

View File

@@ -21,7 +21,7 @@
</field>
<field name="r_shipping_status" width="100"/>
<field name="r_lot_status" width="80"/>
<field name="r_lot_av" width="80"/>
<field name="r_lot_av" width="80" optional="1" tree_invisible="1"/>
<field name="r_lot_quantity" symbol="r_lot_unit_line" width="120" sum="1"/>
<field name="r_lot_matched" symbol="r_lot_unit_line" width="120" sum="1"/>
<field name="r_tot" symbol="r_lot_unit_line" width="120" sum="1"/>