diff --git a/CHANGELOG b/CHANGELOG index aeba0cdb53..4752c937e9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Propagate exception handling from sale to purchase + Version 3.2.0 - 2014-04-21 * Bug fixes (see mercurial logs for details) diff --git a/__init__.py b/__init__.py index 4169055789..2b173b1fc1 100644 --- a/__init__.py +++ b/__init__.py @@ -21,4 +21,6 @@ def register(): module='sale_supply_drop_shipment', type_='model') Pool.register( CreatePurchase, + PurchaseHandleShipmentException, + SaleHandleShipmentException, module='sale_supply_drop_shipment', type_='wizard') diff --git a/purchase.py b/purchase.py index 907d60a19b..e567c62df5 100644 --- a/purchase.py +++ b/purchase.py @@ -3,10 +3,11 @@ from trytond.model import fields from trytond.pyson import Eval from trytond.pool import Pool, PoolMeta +from trytond.transaction import Transaction __all__ = ['PurchaseRequest', 'Purchase', 'PurchaseLine', 'ProductSupplier', - 'CreatePurchase'] + 'CreatePurchase', 'PurchaseHandleShipmentException'] __metaclass__ = PoolMeta @@ -119,3 +120,45 @@ class CreatePurchase: if request.delivery_address else None), ) return result + + +class PurchaseHandleShipmentException: + __name__ = 'purchase.handle.shipment.exception' + + def transition_handle(self): + pool = Pool() + Sale = pool.get('sale.sale') + SaleLine = pool.get('sale.line') + + super(PurchaseHandleShipmentException, self).transition_handle() + + to_recreate = self.ask.recreate_moves + domain_moves = self.ask.domain_moves + sales, saleline_write = set(), [] + sale_lines = SaleLine.search([ + ('purchase_request.purchase_line.purchase', '=', + Transaction().context['active_id']), + ]) + saleline_write = [] + for sale_line in sale_lines: + moves_ignored = [] + moves_recreated = [] + skip = set(sale_line.moves_ignored) + skip.update(sale_line.moves_recreated) + for move in sale_line.moves: + if move not in domain_moves or move in skip: + continue + if move in to_recreate: + moves_recreated.append(move.id) + else: + moves_ignored.append(move.id) + sales.add(sale_line.sale) + saleline_write.append([sale_line]) + saleline_write.append({ + 'moves_ignored': [('add', moves_ignored)], + 'moves_recreated': [('add', moves_recreated)], + }) + + SaleLine.write(*saleline_write) + Sale.process(list(sales)) + return 'end' diff --git a/sale.py b/sale.py index 5975d83fb7..fb448aa229 100644 --- a/sale.py +++ b/sale.py @@ -1,6 +1,6 @@ #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 itertools import groupby +from itertools import groupby, chain from functools import partial from trytond.model import fields from trytond.pyson import Eval @@ -8,7 +8,7 @@ from trytond.pool import Pool, PoolMeta from trytond.transaction import Transaction from trytond.const import OPERATORS -__all__ = ['Sale', 'SaleLine'] +__all__ = ['Sale', 'SaleLine', 'SaleHandleShipmentException'] __metaclass__ = PoolMeta @@ -181,3 +181,47 @@ class SaleLine: i += 1 return domain return super(SaleLine, cls).search(process(domain), *args, **kwargs) + + +class SaleHandleShipmentException: + __name__ = 'sale.handle.shipment.exception' + + def transition_handle(self): + pool = Pool() + Purchase = pool.get('purchase.purchase') + PurchaseLine = pool.get('purchase.line') + Sale = pool.get('sale.sale') + + super(SaleHandleShipmentException, self).transition_handle() + + to_recreate = self.ask.recreate_moves + domain_moves = self.ask.domain_moves + purchases, purchaseline_write = set(), [] + sale = Sale(Transaction().context['active_id']) + for line in sale.lines: + if not line.purchase_request: + continue + purchase_line = line.purchase_request.purchase_line + if not purchase_line: + continue + + moves_ignored = [] + moves_recreated = [] + skip = set(purchase_line.moves_ignored) + skip.update(purchase_line.moves_recreated) + for move in purchase_line.moves: + if move not in domain_moves or move in skip: + continue + if move in to_recreate: + moves_recreated.append(move.id) + else: + moves_ignored.append(move.id) + purchases.add(purchase_line.purchase) + purchaseline_write.append(([purchase_line], { + 'moves_ignored': [('add', moves_ignored)], + 'moves_recreated': [('add', moves_recreated)], + })) + + PurchaseLine.write(*chain(*purchaseline_write)) + Purchase.process(list(purchases)) + return 'end' diff --git a/tests/scenario_sale_supply_drop_shipment.rst b/tests/scenario_sale_supply_drop_shipment.rst index 2856bc56f7..220dd044a2 100644 --- a/tests/scenario_sale_supply_drop_shipment.rst +++ b/tests/scenario_sale_supply_drop_shipment.rst @@ -288,7 +288,8 @@ The purchase is now waiting for this new drop shipment:: >>> purchase.shipment_state u'waiting' -Let's cancel it and handle the problem from the sale order:: +Let's cancel the shipment and handle the issue from the purchase. +As a consequence the sale order is over:: >>> config.user = stock_user.id >>> shipment.click('cancel') @@ -306,20 +307,9 @@ Let's cancel it and handle the problem from the sale order:: u'received' >>> config.user = sale_user.id - >>> sale.shipment_state - u'exception' - >>> handle_exception = Wizard('sale.handle.shipment.exception', [sale]) - >>> handle_exception.execute('handle') >>> sale.reload() >>> sale.shipment_state - u'waiting' - -The sale has created a new outgoing shipment:: - - >>> shipment, = sale.shipments - >>> move, = shipment.outgoing_moves - >>> move.quantity - 150.0 + u'sent' Cancelling the workflow on the purchase step:: @@ -403,3 +393,51 @@ If we opt not to cancel the shipment and deliver from stock:: >>> config.user = sale_user.id >>> sale.shipment_state u'sent' + +Now let's handle the exception on the sale side:: + + >>> config.user = sale_user.id + >>> sale = Sale() + >>> sale.party = customer + >>> sale.payment_term = payment_term + >>> sale_line = sale.lines.new() + >>> sale_line.product = product + >>> sale_line.quantity = 375 + >>> sale.save() + >>> sale.click('quote') + >>> sale.click('confirm') + >>> sale.click('process') + + >>> config.user = purchase_user.id + >>> purchase_request, = PurchaseRequest.find([('purchase_line', '=', None)]) + >>> purchase_request.quantity + 375.0 + >>> create_purchase = Wizard('purchase.request.create_purchase', + ... [purchase_request]) + >>> purchase, = Purchase.find([('state', '=', 'draft')]) + >>> purchase.payment_term = payment_term + >>> purchase.click('quote') + >>> purchase.click('confirm') + >>> purchase.click('process') + >>> purchase.shipment_state + u'waiting' + >>> config.user = sale_user.id + >>> sale.reload() + >>> shipment, = sale.drop_shipments + >>> config.user = stock_user.id + >>> shipment.click('cancel') + + >>> config.user = sale_user.id + >>> sale.shipment_state + u'exception' + >>> handle_exception = Wizard('sale.handle.shipment.exception', [sale]) + >>> _ = handle_exception.form.recreate_moves.pop() + >>> handle_exception.execute('handle') + >>> sale.reload() + >>> sale.shipment_state + u'sent' + + >>> config.user = purchase_user.id + >>> purchase.reload() + >>> purchase.shipment_state + u'received'