Use task queue to process purchase

It adds configured delay for processing purchase which provides a grace period.
And it allows to reset to draft confirmed but not processed purchase.

issue7463
review49401002
This commit is contained in:
Cédric Krier
2018-07-27 09:27:49 +02:00
parent ff0a58d446
commit 98b86f6a5e
9 changed files with 84 additions and 142 deletions
+2
View File
@@ -1,3 +1,5 @@
* Add configured delay for processing purchase
* Allow to reset to draft confirmed but not processed purchase
* Show number and reference in purchase rec_name
* Remove support for Python 2.7
* Invoice purchased product instead of received product
+4
View File
@@ -37,6 +37,10 @@ class Configuration(
]))
purchase_invoice_method = fields.MultiValue(purchase_invoice_method)
get_purchase_invoice_method = get_purchase_methods('invoice_method')
purchase_process_after = fields.TimeDelta("Process Purchase after",
help="The grace period during which confirmed purchase "
"can still be reset to draft.\n"
"Applied only if a worker queue is activated.")
@classmethod
def multivalue_model(cls, field):
+8 -10
View File
@@ -18,11 +18,11 @@ def process_purchase(func):
pool = Pool()
Purchase = pool.get('purchase.purchase')
with Transaction().set_context(_check_access=False):
purchases = Purchase.browse(
set(p for i in cls.browse(invoices) for p in i.purchases))
purchases = set(
p for i in cls.browse(invoices) for p in i.purchases)
func(cls, invoices)
with Transaction().set_context(_check_access=False):
Purchase.process(purchases)
if purchases:
Purchase.__queue__.process(purchases)
return wrapper
@@ -155,11 +155,9 @@ class InvoiceLine(metaclass=PoolMeta):
pool = Pool()
Purchase = pool.get('purchase.purchase')
with Transaction().set_context(_check_access=False):
lines = cls.browse(lines)
invoices = (l.invoice for l in lines
invoices = (l.invoice for l in cls.browse(lines)
if l.type == 'line' and l.invoice)
purchases = Purchase.browse(
set(p for i in invoices for p in i.purchases))
purchases = set(p for i in invoices for p in i.purchases)
super(InvoiceLine, cls).delete(lines)
with Transaction().set_context(_check_access=False):
Purchase.process(purchases)
if purchases:
Purchase.__queue__.process(purchases)
+12 -10
View File
@@ -208,6 +208,7 @@ class Purchase(Workflow, ModelSQL, ModelView, TaxableMixin):
('draft', 'quotation'),
('quotation', 'confirmed'),
('confirmed', 'processing'),
('confirmed', 'draft'),
('processing', 'processing'),
('processing', 'done'),
('done', 'processing'),
@@ -222,7 +223,8 @@ class Purchase(Workflow, ModelSQL, ModelView, TaxableMixin):
'depends': ['state'],
},
'draft': {
'invisible': ~Eval('state').in_(['cancel', 'quotation']),
'invisible': ~Eval('state').in_(
['cancel', 'quotation', 'confirmed']),
'icon': If(Eval('state') == 'cancel', 'tryton-clear',
'tryton-go-previous'),
'depends': ['state'],
@@ -850,8 +852,15 @@ class Purchase(Workflow, ModelSQL, ModelView, TaxableMixin):
@ModelView.button
@Workflow.transition('confirmed')
def confirm(cls, purchases):
pool = Pool()
Configuration = pool.get('purchase.configuration')
cls.set_purchase_date(purchases)
cls.store_cache(purchases)
config = Configuration(1)
with Transaction().set_context(
queue_name='purchase',
queue_scheduled_at=config.purchase_process_after):
cls.__queue__.process(purchases)
@classmethod
@Workflow.transition('processing')
@@ -873,13 +882,6 @@ class Purchase(Workflow, ModelSQL, ModelView, TaxableMixin):
def handle_shipment_exception(cls, purchases):
pass
@classmethod
def cron_process(cls):
purchases = cls.search([
('state', '=', 'confirmed'),
])
cls.process(purchases)
@classmethod
@ModelView.button
def process(cls, purchases):
@@ -1740,7 +1742,7 @@ class HandleShipmentException(Wizard):
'moves_recreated': [('add', moves_recreated)],
})
Purchase.process([purchase])
Purchase.__queue__.process([purchase])
return 'end'
@@ -1801,7 +1803,7 @@ class HandleInvoiceException(Wizard):
'invoices_recreated': [('add', invoices_recreated)],
})
Purchase.process([purchase])
Purchase.__queue__.process([purchase])
return 'end'
-23
View File
@@ -361,29 +361,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="action" ref="report_purchase"/>
</record>
<record model="res.user" id="user_process_purchase">
<field name="login">user_cron_process_purchase</field>
<field name="name">Cron Process Purchase</field>
<field name="signature"></field>
<field name="active" eval="False"/>
</record>
<record model="res.user-res.group"
id="user_process_purchase_group_purchase">
<field name="user" ref="user_process_purchase"/>
<field name="group" ref="group_purchase"/>
</record>
<record model="ir.cron" id="cron_process_purchase">
<field name="name">Process Confirmed Purchases</field>
<field name="request_user" ref="res.user_admin"/>
<field name="user" ref="user_process_purchase"/>
<field name="interval_number" eval="1"/>
<field name="interval_type">hours</field>
<field name="repeat_missed" eval="False"/>
<field name="model">purchase.purchase</field>
<field name="function">cron_process</field>
</record>
<record model="ir.ui.view" id="purchase_line_view_form">
<field name="model">purchase.line</field>
<field name="type">form</field>
+55 -85
View File
@@ -1,5 +1,7 @@
# 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 sql import Null
from sql.aggregate import Max
from sql.operators import Concat
@@ -14,6 +16,22 @@ from trytond.modules.product import price_digits
__all__ = ['ShipmentIn', 'ShipmentInReturn', 'Move', 'Location']
def process_purchase(moves_field):
def _process_purchase(func):
@wraps(func)
def wrapper(cls, shipments):
pool = Pool()
Purchase = pool.get('purchase.purchase')
with Transaction().set_context(_check_access=False):
purchases = set(m.purchase for s in cls.browse(shipments)
for m in getattr(s, moves_field) if m.purchase)
func(cls, shipments)
if purchases:
Purchase.__queue__.process(purchases)
return wrapper
return _process_purchase
class ShipmentIn(metaclass=PoolMeta):
__name__ = 'stock.shipment.in'
@@ -38,35 +56,6 @@ class ShipmentIn(metaclass=PoolMeta):
'it was generated by a purchase.'),
})
@classmethod
def write(cls, *args):
pool = Pool()
Purchase = pool.get('purchase.purchase')
PurchaseLine = pool.get('purchase.line')
super(ShipmentIn, cls).write(*args)
actions = iter(args)
for shipments, values in zip(actions, actions):
if values.get('state') not in ('received', 'cancel'):
continue
purchases = []
move_ids = []
for shipment in shipments:
move_ids.extend([x.id for x in shipment.incoming_moves])
purchase_lines = PurchaseLine.search([
('moves', 'in', move_ids),
])
if purchase_lines:
for purchase_line in purchase_lines:
if purchase_line.purchase not in purchases:
purchases.append(purchase_line.purchase)
with Transaction().set_context(_check_access=False):
purchases = Purchase.browse([p.id for p in purchases])
Purchase.process(purchases)
@classmethod
@ModelView.button
@Workflow.transition('draft')
@@ -80,6 +69,20 @@ class ShipmentIn(metaclass=PoolMeta):
return super(ShipmentIn, cls).draft(shipments)
@classmethod
@ModelView.button
@Workflow.transition('received')
@process_purchase('incoming_moves')
def receive(cls, shipments):
super(ShipmentIn, cls).receive(shipments)
@classmethod
@ModelView.button
@Workflow.transition('cancel')
@process_purchase('incoming_moves')
def cancel(cls, shipments):
super(ShipmentIn, cls).cancel(shipments)
class ShipmentInReturn(metaclass=PoolMeta):
__name__ = 'stock.shipment.in.return'
@@ -121,34 +124,6 @@ class ShipmentInReturn(metaclass=PoolMeta):
values=[value]))
super(ShipmentInReturn, cls).__register__(module_name)
@classmethod
def write(cls, *args):
pool = Pool()
Purchase = pool.get('purchase.purchase')
PurchaseLine = pool.get('purchase.line')
super(ShipmentInReturn, cls).write(*args)
actions = iter(args)
for shipments, values in zip(actions, actions):
if values.get('state') != 'done':
continue
move_ids = []
for shipment in shipments:
move_ids.extend([x.id for x in shipment.moves])
purchase_lines = PurchaseLine.search([
('moves', 'in', move_ids),
])
purchases = set()
if purchase_lines:
for purchase_line in purchase_lines:
purchases.add(purchase_line.purchase)
with Transaction().set_context(_check_access=False):
purchases = Purchase.browse([p.id for p in purchases])
Purchase.process(purchases)
@classmethod
@ModelView.button
@Workflow.transition('draft')
@@ -162,6 +137,27 @@ class ShipmentInReturn(metaclass=PoolMeta):
return super(ShipmentInReturn, cls).draft(shipments)
@classmethod
@ModelView.button
@Workflow.transition('done')
@process_purchase('moves')
def done(cls, shipments):
super(ShipmentInReturn, cls).done(shipments)
def process_purchase_move(func):
@wraps(func)
def wrapper(cls, moves):
pool = Pool()
Purchase = pool.get('purchase.purchase')
with Transaction().set_context(_check_access=False):
purchases = set(
m.purchase for m in cls.browse(moves) if m.purchase)
func(cls, moves)
if purchases:
Purchase.__queue__.process(purchases)
return wrapper
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
@@ -314,41 +310,15 @@ class Move(metaclass=PoolMeta):
@classmethod
@ModelView.button
@Workflow.transition('cancel')
@process_purchase_move
def cancel(cls, moves):
pool = Pool()
Purchase = pool.get('purchase.purchase')
PurchaseLine = pool.get('purchase.line')
super(Move, cls).cancel(moves)
purchase_lines = PurchaseLine.search([
('moves', 'in', [m.id for m in moves]),
])
if purchase_lines:
purchase_ids = list(set(l.purchase.id for l in purchase_lines))
purchases = Purchase.browse(purchase_ids)
Purchase.process(purchases)
@classmethod
@process_purchase_move
def delete(cls, moves):
pool = Pool()
Purchase = pool.get('purchase.purchase')
PurchaseLine = pool.get('purchase.line')
purchases = set()
purchase_lines = PurchaseLine.search([
('moves', 'in', [m.id for m in moves]),
])
super(Move, cls).delete(moves)
if purchase_lines:
for purchase_line in purchase_lines:
purchases.add(purchase_line.purchase)
if purchases:
with Transaction().set_context(_check_access=False):
purchases = Purchase.browse([p.id for p in purchases])
Purchase.process(purchases)
class Location(metaclass=PoolMeta):
__name__ = 'stock.location'
-11
View File
@@ -153,9 +153,6 @@ Purchase 5 products::
>>> purchase.click('confirm')
>>> purchase.untaxed_amount, purchase.tax_amount, purchase.total_amount
(Decimal('25.00'), Decimal('2.50'), Decimal('27.50'))
>>> purchase.click('process')
>>> purchase.untaxed_amount, purchase.tax_amount, purchase.total_amount
(Decimal('25.00'), Decimal('2.50'), Decimal('27.50'))
>>> purchase.state
'processing'
>>> purchase.shipment_state
@@ -215,7 +212,6 @@ Purchase 5 products with an invoice method 'on shipment'::
>>> purchase_line.quantity = 3.0
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> purchase.click('process')
>>> purchase.state
'processing'
>>> purchase.shipment_state
@@ -309,7 +305,6 @@ Create a Return::
>>> return_line.description = 'Comment'
>>> return_.click('quote')
>>> return_.click('confirm')
>>> return_.click('process')
>>> return_.state
'processing'
>>> return_.shipment_state
@@ -376,7 +371,6 @@ Mixing return and purchase::
>>> mixline2.quantity = -2.
>>> mix.click('quote')
>>> mix.click('confirm')
>>> mix.click('process')
>>> mix.state
'processing'
>>> mix.shipment_state
@@ -445,7 +439,6 @@ Mixing stuff with an invoice method 'on shipment'::
>>> mixline2.quantity = -3.
>>> mix.click('quote')
>>> mix.click('confirm')
>>> mix.click('process')
>>> mix.state
'processing'
>>> mix.shipment_state
@@ -492,7 +485,6 @@ Purchase services::
>>> service_purchase.save()
>>> service_purchase.click('quote')
>>> service_purchase.click('confirm')
>>> service_purchase.click('process')
>>> service_purchase.state
'processing'
>>> service_purchase.shipment_state
@@ -535,7 +527,6 @@ linked to invoices::
>>> line.quantity = 10.0
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> purchase.click('process')
>>> shipment = ShipmentIn()
>>> shipment.supplier = supplier
>>> for move in purchase.moves:
@@ -570,7 +561,6 @@ correctly linked to invoices::
>>> line.quantity = 10.0
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> purchase.click('process')
>>> shipment = ShipmentIn()
>>> shipment.supplier = supplier
>>> for move in purchase.moves:
@@ -607,7 +597,6 @@ Deleting a line from a invoice should recreate it::
>>> line.quantity = 10.0
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> purchase.click('process')
>>> invoice, = purchase.invoices
>>> invoice_line, = invoice.lines
>>> invoice.lines.remove(invoice_line)
-3
View File
@@ -46,9 +46,6 @@ Create empty purchase::
Decimal('0')
>>> purchase.click('confirm')
>>> purchase.state
'confirmed'
>>> purchase.click('process')
>>> purchase.state
'done'
>>> purchase.shipment_state
'none'
+3
View File
@@ -6,4 +6,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="purchase_sequence"/>
<label name="purchase_invoice_method" />
<field name="purchase_invoice_method" />
<newline/>
<label name="purchase_process_after"/>
<field name="purchase_process_after"/>
</form>