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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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'
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user