From 18e96c6be2cc1d74c98ad04bd7995c1f6ab73660 Mon Sep 17 00:00:00 2001 From: "Tiffany Chang (tic)" Date: Fri, 28 Feb 2020 14:45:18 +0000 Subject: [PATCH] [IMP] purchase(_stock): Add more and update PO dates Adds "Expected Date" and "Effective Date" fields to purchase orders to match what exists in sales orders. "Effective Date" mirrors sales logic (= first completed stock move). "Expected Date" has simplier logic due to simplier nature of receiving stock vs shipping (= earliest "Scheduled Date" of all PO lines). Form view has been updated to include these new fields. Additionally code was changed to not have PO 'order_line.date_planned' be overwritten by the 'purchase_order.date_planned' value. Previous behavior was both confusing and made it so info was lost. Also logic for related stock move picking line expected date values has been updated to now consider 'purchase_order.date_planned' value if set. Related changes include: - not overwriting when PO is edited+saved (this only occurred in write() logic, not create() which caused inconsistent behavior anyways.), - updating related tests, - removing PO line date_planned editing restriction when PO date_planned is set - updating of relevant stock moves/picking expected date due to PO.line.propogate_date = True ('purchase_order.date_planned' if exists, otherwise 'order_line.date_planned') This change also includes some small related improvements: - Removal of outdated code comment. - Addition of Help strings to better explain fields. - Prevent copy of 'date_planned' value into new POs (now mirrors sales logic). This change corresponds to "Receipt Date on PO's" subsection of Purchase KPIs task. Task #2198420 --- addons/purchase/models/purchase.py | 29 +++++---- addons/purchase/tests/test_purchase.py | 64 ++++--------------- addons/purchase/views/purchase_views.xml | 7 +- addons/purchase_stock/models/purchase.py | 40 +++++++++--- .../tests/test_purchase_lead_time.py | 1 - .../purchase_stock/views/purchase_views.xml | 7 ++ 6 files changed, 70 insertions(+), 78 deletions(-) diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index dc52d85cae7..5b492df2691 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -96,10 +96,11 @@ class PurchaseOrder(models.Model): ('to invoice', 'Waiting Bills'), ('invoiced', 'Fully Billed'), ], string='Billing Status', compute='_get_invoiced', store=True, readonly=True, copy=False, default='no') - - # There is no inverse function on purpose since the date may be different on each line - date_planned = fields.Datetime(string='Receipt Date', index=True, - help='This is the Receipt Date promised by the supplier. If set, the receipt will be scheduled at this date.') + date_planned = fields.Datetime( + string='Receipt Date', index=True, copy=False, + help="Delivery date promised by vendor. This date is used to determine expected arrival of products.") + expected_date = fields.Datetime("Expected Date", compute='_compute_expected_date', store=True, + help="Delivery date expected by vendor, computed from the minimum lead time of the order lines.") date_calendar_start = fields.Datetime(compute='_compute_date_calendar_start', readonly=True, store=True) amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', tracking=True) @@ -149,6 +150,13 @@ class PurchaseOrder(models.Model): for order in self: order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.currency_id, order.company_id, order.date_order) + @api.depends('order_line.date_planned') + def _compute_expected_date(self): + """ expected_date = the earliest date_planned across all order lines. """ + for order in self: + dates_list = order.order_line.mapped('date_planned') + order.expected_date = fields.Datetime.to_string(min(dates_list)) if dates_list else False + @api.depends('name', 'partner_ref') def name_get(self): result = [] @@ -170,12 +178,6 @@ class PurchaseOrder(models.Model): vals['name'] = self.env['ir.sequence'].next_by_code('purchase.order', sequence_date=seq_date) or '/' return super(PurchaseOrder, self).create(vals) - def write(self, vals): - res = super(PurchaseOrder, self).write(vals) - if vals.get('date_planned'): - self.order_line.filtered(lambda line: not line.display_type).date_planned = vals['date_planned'] - return res - def unlink(self): for order in self: if not order.state == 'cancel': @@ -188,9 +190,7 @@ class PurchaseOrder(models.Model): self = self.with_context(ctx) new_po = super(PurchaseOrder, self).copy(default=default) for line in new_po.order_line: - if new_po.date_planned: - line.date_planned = new_po.date_planned - elif line.product_id: + if line.product_id: seller = line.product_id._select_seller( partner_id=line.partner_id, quantity=line.product_qty, date=line.order_id.date_order and line.order_id.date_order.date(), uom_id=line.product_uom) @@ -539,7 +539,8 @@ class PurchaseOrderLine(models.Model): sequence = fields.Integer(string='Sequence', default=10) product_qty = fields.Float(string='Quantity', digits='Product Unit of Measure', required=True) product_uom_qty = fields.Float(string='Total Quantity', compute='_compute_product_uom_qty', store=True) - date_planned = fields.Datetime(string='Scheduled Date', index=True) + date_planned = fields.Datetime(string='Scheduled Date', index=True, + help="Delivery date expected from vendor. This date respectively defaults to vendor pricelist lead time then today's date.") taxes_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)]) product_uom = fields.Many2one('uom.uom', string='Unit of Measure', domain="[('category_id', '=', product_uom_category_id)]") product_uom_category_id = fields.Many2one(related='product_id.uom_id.category_id') diff --git a/addons/purchase/tests/test_purchase.py b/addons/purchase/tests/test_purchase.py index 996eefa6640..d25436d5562 100644 --- a/addons/purchase/tests/test_purchase.py +++ b/addons/purchase/tests/test_purchase.py @@ -22,11 +22,10 @@ class TestPurchase(SavepointCase): cls.vendor = cls.env['res.partner'].create({'name': 'vendor1'}) cls.uom_unit = cls.env.ref('uom.product_uom_unit') - def test_date_planned_1(self): - """Set a date planned on a PO, see that it is set on the PO lines. Try to edit the date - planned of the PO line, see that it is not possible. Unset the date planned on the PO and - edit the date planned on the PO lines. Validate the PO and see that it isn't possible to - set the date planned on the PO nor on the PO lines. + def test_expected_date(self): + """Set a date planned on 2 PO lines. Check that the PO expected date is the earliest PO line date + planned. Change one of the dates so it is even earlier and check that the expected date is set to + this earlier date. """ po = Form(self.env['purchase.order']) po.partner_id = self.vendor @@ -40,57 +39,22 @@ class TestPurchase(SavepointCase): po_line.price_unit = 200 po = po.save() - # Check there is no date planned on the PO and the same date planned on both PO lines. - self.assertEqual(po.date_planned, False) + # Check that the same date is planned on both PO lines. self.assertNotEqual(po.order_line[0].date_planned, False) self.assertAlmostEqual(po.order_line[0].date_planned, po.order_line[1].date_planned, delta=timedelta(seconds=10)) + self.assertAlmostEqual(po.order_line[0].date_planned, po.expected_date, delta=timedelta(seconds=10)) orig_date_planned = po.order_line[0].date_planned - # Set a date planned on a PO, see that it is set on the PO lines. - new_date_planned = orig_date_planned + timedelta(hours=1) - po.date_planned = new_date_planned - self.assertAlmostEqual(po.order_line[0].date_planned, new_date_planned, delta=timedelta(seconds=10)) - self.assertAlmostEqual(po.order_line[1].date_planned, new_date_planned, delta=timedelta(seconds=10)) + # Set an earlier date planned on a PO line and check that the PO expected date matches it. + new_date_planned = orig_date_planned - timedelta(hours=1) + po.order_line[0].date_planned = new_date_planned + self.assertAlmostEqual(po.order_line[0].date_planned, po.expected_date, delta=timedelta(seconds=10)) - # Try to edit the date planned of the PO line, see that it is not possible - po = Form(po) - with self.assertRaises(AssertionError): - po.order_line.edit(0).date_planned = orig_date_planned - with self.assertRaises(AssertionError): - po.order_line.edit(1).date_planned = orig_date_planned - po = po.save() - - self.assertAlmostEqual(po.order_line[0].date_planned, new_date_planned, delta=timedelta(seconds=10)) - self.assertAlmostEqual(po.order_line[1].date_planned, new_date_planned, delta=timedelta(seconds=10)) - - # Unset the date planned on the PO and edit the date planned on the PO line. - po = Form(po) - po.date_planned = False - with po.order_line.edit(0) as po_line: - po_line.date_planned = orig_date_planned - with po.order_line.edit(1) as po_line: - po_line.date_planned = orig_date_planned - po = po.save() - - self.assertAlmostEqual(po.order_line[0].date_planned, orig_date_planned, delta=timedelta(seconds=10)) - self.assertAlmostEqual(po.order_line[1].date_planned, orig_date_planned, delta=timedelta(seconds=10)) - - # Validate the PO and see that it isn't possible to set the date planned on the PO - # nor on the PO lines. - po.button_confirm() - po.button_done() - - po = Form(po) - with self.assertRaises(AssertionError): - po.date_planned = new_date_planned - with self.assertRaises(AssertionError): - with po.order_line.edit(0) as po_line: - po_line.date_planned = orig_date_planned - with self.assertRaises(AssertionError): - with po.order_line.edit(1) as po_line: - po_line.date_planned = orig_date_planned - po.save() + # Set an even earlier date planned on the other PO line and check that the PO expected date matches it. + new_date_planned = orig_date_planned - timedelta(hours=72) + po.order_line[1].date_planned = new_date_planned + self.assertAlmostEqual(po.order_line[1].date_planned, po.expected_date, delta=timedelta(seconds=10)) def test_purchase_order_sequence(self): PurchaseOrder = self.env['purchase.order'].with_context(tracking_disable=True) diff --git a/addons/purchase/views/purchase_views.xml b/addons/purchase/views/purchase_views.xml index da5d47989b4..7c2ddb8e1a3 100644 --- a/addons/purchase/views/purchase_views.xml +++ b/addons/purchase/views/purchase_views.xml @@ -209,7 +209,7 @@ context="{'partner_id':parent.partner_id, 'quantity':product_qty,'uom':product_uom, 'company_id': parent.company_id}" force_save="1" domain="[('purchase_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]"/> - + @@ -342,8 +342,9 @@