diff --git a/modules/purchase_trade/docs/business-rules.md b/modules/purchase_trade/docs/business-rules.md index ae10214..aae36ba 100644 --- a/modules/purchase_trade/docs/business-rules.md +++ b/modules/purchase_trade/docs/business-rules.md @@ -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`. diff --git a/modules/purchase_trade/lot.py b/modules/purchase_trade/lot.py index e0abdde..46ce27b 100755 --- a/modules/purchase_trade/lot.py +++ b/modules/purchase_trade/lot.py @@ -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) diff --git a/modules/purchase_trade/view/lot_report_context_form.xml b/modules/purchase_trade/view/lot_report_context_form.xml index b854c3a..805d5ce 100755 --- a/modules/purchase_trade/view/lot_report_context_form.xml +++ b/modules/purchase_trade/view/lot_report_context_form.xml @@ -9,6 +9,10 @@