diff --git a/purchase.py b/purchase.py index 66bb72190c..703e5ba454 100644 --- a/purchase.py +++ b/purchase.py @@ -1,14 +1,26 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. +from functools import wraps -from trytond.pool import PoolMeta -from trytond.model import fields +from trytond.pool import PoolMeta, Pool +from trytond.model import ModelView, Workflow, fields from trytond.transaction import Transaction from trytond.pyson import Eval __all__ = ['Purchase', 'PurchaseLine'] +def process_request(func): + @wraps(func) + def wrapper(cls, purchases): + pool = Pool() + Request = pool.get('purchase.request') + func(cls, purchases) + requests = [r for p in purchases for l in p.lines for r in l.requests] + Request.update_state(requests) + return wrapper + + class Purchase: __name__ = 'purchase.purchase' __metaclass__ = PoolMeta @@ -37,6 +49,19 @@ class Purchase: 'purchase': purchase.rec_name, }) + @classmethod + @ModelView.button + @Workflow.transition('cancel') + @process_request + def cancel(cls, purchases): + super(Purchase, cls).cancel(purchases) + + @classmethod + @Workflow.transition('done') + @process_request + def do(cls, purchases): + super(Purchase, cls).do(purchases) + class PurchaseLine: __metaclass__ = PoolMeta @@ -47,3 +72,13 @@ class PurchaseLine: states={ 'invisible': ~Eval('requests'), }) + + @classmethod + def delete(cls, lines): + pool = Pool() + Request = pool.get('purchase.request') + with Transaction().set_context(_check_access=False): + requests = [r for l in cls.browse(lines) for r in l.requests] + super(PurchaseLine, cls).delete(lines) + with Transaction().set_context(_check_access=False): + Request.update_state(requests) diff --git a/purchase_request.py b/purchase_request.py index 1b8951aba5..1f1b067ee8 100644 --- a/purchase_request.py +++ b/purchase_request.py @@ -5,9 +5,6 @@ from decimal import Decimal from itertools import groupby from functools import partial -from sql import Null -from sql.conditionals import Case - from trytond import backend from trytond.model import ModelView, ModelSQL, fields from trytond.wizard import Wizard, StateView, StateTransition, Button @@ -82,13 +79,13 @@ class PurchaseRequest(ModelSQL, ModelView): origin = fields.Reference('Origin', selection='get_origin', readonly=True, required=True) exception_ignored = fields.Boolean('Ignored Exception') - state = fields.Function(fields.Selection([ - ('purchased', 'Purchased'), - ('done', 'Done'), - ('draft', 'Draft'), - ('cancel', 'Cancel'), - ('exception', 'Exception'), - ], 'State'), 'get_state', searcher='search_state') + state = fields.Selection([ + ('purchased', "Purchased"), + ('done', "Done"), + ('draft', "Draft"), + ('cancel', "Cancel"), + ('exception', "Exception"), + ], "State", required=True, select=True) @classmethod def __setup__(cls): @@ -110,8 +107,17 @@ class PurchaseRequest(ModelSQL, ModelView): def __register__(cls, module_name): pool = Pool() ModelData = pool.get('ir.model.data') + Purchase = pool.get('purchase.purchase') + PurchaseLine = pool.get('purchase.line') TableHandler = backend.get('TableHandler') model_data = ModelData.__table__() + purchase = Purchase.__table__() + purchase_line = PurchaseLine.__table__() + request = cls.__table__() + + tablehandler = TableHandler(cls, module_name) + state_exist = tablehandler.column_exist('state') + super(PurchaseRequest, cls).__register__(module_name) # Migration from 3.6: removing the constraint on the quantity @@ -130,6 +136,31 @@ class PurchaseRequest(ModelSQL, ModelView): tablehandler.not_null_action('product', action='remove') tablehandler.not_null_action('uom', action='remove') + # Migration from 4.2: add state + if not state_exist: + cursor = Transaction().connection.cursor() + update = Transaction().connection.cursor() + query = request.join(purchase_line, type_='INNER', + condition=request.purchase_line == purchase_line.id + ).join(purchase, type_='INNER', + condition=purchase_line.purchase == purchase.id + ).select( + request.id, purchase.state, request.exception_ignored) + cursor.execute(*query) + for request_id, purchase_state, exception_ignored in cursor: + if purchase_state == 'cancel' and not exception_ignored: + state = 'exception' + elif purchase_state == 'cancel': + state = 'cancel' + elif purchase_state == 'done': + state = 'done' + else: + state = 'purchased' + update.execute(*request.update( + [request.state], + [state], + where=request.id == request_id)) + def get_rec_name(self, name): product_name = (self.product.name if self.product else self.description.splitlines()[0]) @@ -169,7 +200,11 @@ class PurchaseRequest(ModelSQL, ModelView): def currency(self): return self.company.currency - def get_state(self, name): + @classmethod + def default_state(cls): + return 'draft' + + def get_state(self): if self.purchase_line: if (self.purchase_line.purchase.state == 'cancel' and not self.exception_ignored): @@ -183,35 +218,12 @@ class PurchaseRequest(ModelSQL, ModelView): return 'draft' @classmethod - def search_state(cls, name, clause): - pool = Pool() - Purchase = pool.get('purchase.purchase') - PurchaseLine = pool.get('purchase.line') - - request = cls.__table__() - purchase_line = PurchaseLine.__table__() - purchase = Purchase.__table__() - - _, operator_, state = clause - Operator = fields.SQL_OPERATORS[operator_] - state_case = Case( - ((purchase.state == 'cancel') - & (request.exception_ignored == False), 'exception'), - ((purchase.state == 'cancel') - & (request.exception_ignored == True), 'cancel'), - (purchase.state == 'done', 'done'), - (request.purchase_line != Null, 'purchased'), - else_='draft') - state_query = request.join( - purchase_line, type_='LEFT', - condition=request.purchase_line == purchase_line.id - ).join(purchase, type_='LEFT', - condition=purchase_line.purchase == purchase.id - ).select( - request.id, - where=Operator(state_case, state)) - - return [('id', 'in', state_query)] + def update_state(cls, requests): + for request in requests: + state = request.get_state() + if state != request.state: + request.state = state + cls.save(requests) def get_warehouse_required(self, name): return self.product and self.product.type in ('goods', 'assets') @@ -423,6 +435,7 @@ class CreatePurchase(Wizard): lines.append(line) Purchase.save(purchases) Line.save(lines) + Request.update_state(requests) return 'end' @staticmethod @@ -492,9 +505,9 @@ class HandlePurchaseCancellationException(Wizard): PurchaseRequest = pool.get('purchase.request') requests = PurchaseRequest.browse(Transaction().context['active_ids']) - PurchaseRequest.write(requests, { - 'purchase_line': None, - }) + for request in requests: + request.purchase_line = None + PurchaseRequest.update_state(requests) return 'end' def transition_cancel_request(self): @@ -502,9 +515,9 @@ class HandlePurchaseCancellationException(Wizard): PurchaseRequest = pool.get('purchase.request') requests = PurchaseRequest.browse(Transaction().context['active_ids']) - PurchaseRequest.write(requests, { - 'exception_ignored': True, - }) + for request in requests: + request.exception_ignored = True + PurchaseRequest.update_state(requests) return 'end' diff --git a/tests/scenario_purchase_request.rst b/tests/scenario_purchase_request.rst index 0ec90bd1ab..366b592c09 100644 --- a/tests/scenario_purchase_request.rst +++ b/tests/scenario_purchase_request.rst @@ -162,14 +162,14 @@ Create the purchase then cancel it:: >>> create_purchase.form.party = supplier >>> create_purchase.execute('start') >>> pr.state - 'purchased' + u'purchased' >>> Purchase = Model.get('purchase.purchase') >>> purchase, = Purchase.find() >>> purchase.click('cancel') >>> pr.reload() >>> pr.state - 'exception' + u'exception' Handle the exception:: @@ -177,20 +177,20 @@ Handle the exception:: ... 'purchase.request.handle.purchase.cancellation', [pr]) >>> handle_exception.execute('reset') >>> pr.state - 'draft' + u'draft' Recreate a purchase and cancel it again:: >>> create_purchase = Wizard('purchase.request.create_purchase', ... [pr]) >>> pr.state - 'purchased' + u'purchased' >>> purchase, = Purchase.find([('state', '=', 'draft')]) >>> purchase.click('cancel') >>> pr.reload() >>> pr.state - 'exception' + u'exception' Handle again the exception:: @@ -198,7 +198,7 @@ Handle again the exception:: ... 'purchase.request.handle.purchase.cancellation', [pr]) >>> handle_exception.execute('cancel_request') >>> pr.state - 'cancel' + u'cancel' Re-create the purchase request:: @@ -229,7 +229,7 @@ Create the purchase with a unique line:: >>> create_purchase.form.party = supplier >>> create_purchase.execute('start') >>> pr.state - 'purchased' + u'purchased' >>> Purchase = Model.get('purchase.purchase') >>> purchase, = Purchase.find([('state', '=', 'draft')]) @@ -258,7 +258,7 @@ Create the purchase without product:: >>> create_purchase.form.party = supplier >>> create_purchase.execute('start') >>> pr.state - 'purchased' + u'purchased' >>> pr.purchase_line.product >>> pr.purchase_line.description