diff options
| author | svs-odoo <svs@odoo.com> | 2019-09-13 06:45:01 +0000 |
|---|---|---|
| committer | svs-odoo <svs@odoo.com> | 2019-10-25 13:50:37 +0000 |
| commit | 197ef3ea687d6816ce6cde2a7dc6009f3bdf1b5f (patch) | |
| tree | 831a531edcc370f78ccce9e206274fdaafbb856f | |
| parent | f30b17b0cdba5cb687ed6a458c260ac882b06247 (diff) | |
[IMP] product_expiry: expired wizard confirmation
When user tries to validate a picking with expired production lot,
displays a wizard for the confirmation popup. This wizard shows which
lots are expired.
Task #1938656
| -rw-r--r-- | addons/product_expiry/__init__.py | 1 | ||||
| -rw-r--r-- | addons/product_expiry/__manifest__.py | 1 | ||||
| -rw-r--r-- | addons/product_expiry/models/__init__.py | 1 | ||||
| -rw-r--r-- | addons/product_expiry/models/stock_picking.py | 38 | ||||
| -rw-r--r-- | addons/product_expiry/tests/test_stock_production_lot.py | 106 | ||||
| -rw-r--r-- | addons/product_expiry/wizard/__init__.py | 3 | ||||
| -rw-r--r-- | addons/product_expiry/wizard/confirm_expiry.py | 52 | ||||
| -rw-r--r-- | addons/product_expiry/wizard/confirm_expiry_view.xml | 34 |
8 files changed, 236 insertions, 0 deletions
diff --git a/addons/product_expiry/__init__.py b/addons/product_expiry/__init__.py index d6210b1285d..33bbab569d0 100644 --- a/addons/product_expiry/__init__.py +++ b/addons/product_expiry/__init__.py @@ -1,3 +1,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import models +from . import wizard diff --git a/addons/product_expiry/__manifest__.py b/addons/product_expiry/__manifest__.py index 1b7b59a4c68..4cb792f72f1 100644 --- a/addons/product_expiry/__manifest__.py +++ b/addons/product_expiry/__manifest__.py @@ -21,6 +21,7 @@ Also implements the removal strategy First Expiry First Out (FEFO) widely used, 'views/product_template_views.xml', 'views/stock_move_views.xml', 'views/stock_quant_views.xml', + 'wizard/confirm_expiry_view.xml', 'report/report_lot_barcode.xml', 'data/product_expiry_data.xml'], } diff --git a/addons/product_expiry/models/__init__.py b/addons/product_expiry/models/__init__.py index a32a1a3e386..ce07f86f046 100644 --- a/addons/product_expiry/models/__init__.py +++ b/addons/product_expiry/models/__init__.py @@ -5,4 +5,5 @@ from . import production_lot from . import product_product from . import stock_move_line from . import stock_move +from . import stock_picking from . import stock_quant diff --git a/addons/product_expiry/models/stock_picking.py b/addons/product_expiry/models/stock_picking.py new file mode 100644 index 00000000000..35d275bacb7 --- /dev/null +++ b/addons/product_expiry/models/stock_picking.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, _ + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + def _pre_action_done_hook(self): + res = super()._pre_action_done_hook() + # We use the 'skip_expired' context key to avoid to make the check when + # user did already confirmed the wizard about expired lots. + if res is True and not self.env.context.get('skip_expired'): + pickings_to_warn_expired = self._check_expired_lots() + if pickings_to_warn_expired: + return pickings_to_warn_expired._action_generate_expired_wizard() + return res + + def _check_expired_lots(self): + expired_pickings = self.move_line_ids.filtered(lambda ml: ml.lot_id.product_expiry_alert).picking_id + return expired_pickings + + def _action_generate_expired_wizard(self): + expired_lot_ids = self.move_line_ids.filtered(lambda ml: ml.lot_id.product_expiry_alert).lot_id.ids + context = dict(self.env.context) + context.update({ + 'default_picking_ids': [(6, 0, self.ids)], + 'default_lot_ids': [(6, 0, expired_lot_ids)], + }) + return { + 'name': _('Confirmation'), + 'type': 'ir.actions.act_window', + 'res_model': 'expiry.picking.confirmation', + 'view_mode': 'form', + 'target': 'new', + 'context': context, + } diff --git a/addons/product_expiry/tests/test_stock_production_lot.py b/addons/product_expiry/tests/test_stock_production_lot.py index 256ab1f17eb..84a9974ac8f 100644 --- a/addons/product_expiry/tests/test_stock_production_lot.py +++ b/addons/product_expiry/tests/test_stock_production_lot.py @@ -313,3 +313,109 @@ class TestStockProductionLot(TestStockCommon): apple_lot.removal_date, expiration_date - timedelta(days=2), delta=time_gap) self.assertAlmostEqual( apple_lot.alert_date, expiration_date - timedelta(days=6), delta=time_gap) + + def test_05_confirmation_on_delivery(self): + """ Test when user tries to delivery expired lot, he/she gets a + confirmation wizard. """ + partner = self.env['res.partner'].create({ + 'name': 'Cider & Son', + 'company_id': self.env.ref('base.main_company').id, + }) + # Creates 3 lots (1 non-expired lot, 2 expired lots) + lot_form = Form(self.LotObj) # Creates the lot. + lot_form.name = 'good-apple-lot' + lot_form.product_id = self.apple_product + lot_form.company_id = self.env.company + good_lot = lot_form.save() + + lot_form = Form(self.LotObj) # Creates the lot. + lot_form.name = 'expired-apple-lot-01' + lot_form.product_id = self.apple_product + lot_form.company_id = self.env.company + expired_lot_1 = lot_form.save() + lot_form = Form(expired_lot_1) # Edits the lot to make it expired. + lot_form.expiration_date = datetime.today() - timedelta(days=10) + expired_lot_1 = lot_form.save() + + # Case #1: make a delivery with no expired lot. + picking_form = Form(self.env['stock.picking']) + picking_form.partner_id = partner + picking_form.picking_type_id = self.env.ref('stock.picking_type_out') + with picking_form.move_ids_without_package.new() as move: + move.product_id = self.apple_product + move.product_uom_qty = 4 + # Saves and confirms it... + delivery_1 = picking_form.save() + delivery_1.action_confirm() + # ... then create a move line with the non-expired lot and valids the picking. + delivery_1.move_line_ids_without_package = [(5, 0), (0, 0, { + 'company_id': self.env.company.id, + 'location_id': delivery_1.move_lines.location_id.id, + 'location_dest_id': delivery_1.move_lines.location_dest_id.id, + 'lot_id': good_lot.id, + 'product_id': self.apple_product.id, + 'product_uom_id': self.apple_product.uom_id.id, + 'qty_done': 4, + })] + res = delivery_1.button_validate() + # Validate a delivery for good products must not raise anything. + self.assertEqual(res, True) + + # Case #2: make a delivery with one non-expired lot and one expired lot. + picking_form = Form(self.env['stock.picking']) + picking_form.partner_id = partner + picking_form.picking_type_id = self.env.ref('stock.picking_type_out') + with picking_form.move_ids_without_package.new() as move: + move.product_id = self.apple_product + move.product_uom_qty = 8 + # Saves and confirms it... + delivery_2 = picking_form.save() + delivery_2.action_confirm() + # ... then create a move line for the non-expired lot and for an expired + # lot and valids the picking. + delivery_2.move_line_ids_without_package = [(5, 0), (0, 0, { + 'company_id': self.env.company.id, + 'location_id': delivery_2.move_lines.location_id.id, + 'location_dest_id': delivery_2.move_lines.location_dest_id.id, + 'lot_id': good_lot.id, + 'product_id': self.apple_product.id, + 'product_uom_id': self.apple_product.uom_id.id, + 'qty_done': 4, + }), (0, 0, { + 'company_id': self.env.company.id, + 'location_id': delivery_2.move_lines.location_id.id, + 'location_dest_id': delivery_2.move_lines.location_dest_id.id, + 'lot_id': expired_lot_1.id, + 'product_id': self.apple_product.id, + 'product_uom_id': self.apple_product.uom_id.id, + 'qty_done': 4, + })] + res = delivery_2.button_validate() + # Validate a delivery containing expired products must raise a confirmation wizard. + self.assertNotEqual(res, True) + self.assertEqual(res['res_model'], 'expiry.picking.confirmation') + + # Case #3: make a delivery with only on expired lot. + picking_form = Form(self.env['stock.picking']) + picking_form.partner_id = partner + picking_form.picking_type_id = self.env.ref('stock.picking_type_out') + with picking_form.move_ids_without_package.new() as move: + move.product_id = self.apple_product + move.product_uom_qty = 4 + # Saves and confirms it... + delivery_3 = picking_form.save() + delivery_3.action_confirm() + # ... then create two move lines with expired lot and valids the picking. + delivery_3.move_line_ids_without_package = [(5, 0), (0, 0, { + 'company_id': self.env.company.id, + 'location_id': delivery_3.move_lines.location_id.id, + 'location_dest_id': delivery_3.move_lines.location_dest_id.id, + 'lot_id': expired_lot_1.id, + 'product_id': self.apple_product.id, + 'product_uom_id': self.apple_product.uom_id.id, + 'qty_done': 4, + })] + res = delivery_3.button_validate() + # Validate a delivery containing expired products must raise a confirmation wizard. + self.assertNotEqual(res, True) + self.assertEqual(res['res_model'], 'expiry.picking.confirmation') diff --git a/addons/product_expiry/wizard/__init__.py b/addons/product_expiry/wizard/__init__.py new file mode 100644 index 00000000000..f96376b2bd2 --- /dev/null +++ b/addons/product_expiry/wizard/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import confirm_expiry diff --git a/addons/product_expiry/wizard/confirm_expiry.py b/addons/product_expiry/wizard/confirm_expiry.py new file mode 100644 index 00000000000..1075d4877b1 --- /dev/null +++ b/addons/product_expiry/wizard/confirm_expiry.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ + + +class ConfirmExpiry(models.TransientModel): + _name = 'expiry.picking.confirmation' + _description = 'Confirm Expiry' + + lot_ids = fields.Many2many('stock.production.lot', readonly=True, required=True) + picking_ids = fields.Many2many('stock.picking', readonly=True) + description = fields.Char('Description', compute='_compute_descriptive_fields') + show_lots = fields.Boolean('Show Lots', compute='_compute_descriptive_fields') + + @api.depends('lot_ids') + def _compute_descriptive_fields(self): + # Shows expired lots only if we are more than one expired lot. + self.show_lots = len(self.lot_ids) > 1 + if self.show_lots: + # For multiple expired lots, they are listed in the wizard view. + self.description = _( + "You are going to deliver some product expired lots." + "\nDo you confirm you want to proceed ?" + ) + else: + # For one expired lot, its name is written in the wizard message. + self.description = _( + "You are going to deliver the product %s, %s which is expired." + "\nDo you confirm you want to proceed ?" % ( + self.lot_ids.product_id.display_name, + self.lot_ids.name, + ) + ) + + def process(self): + picking_to_validate = self.env.context.get('button_validate_picking_ids') + if picking_to_validate: + picking_to_validate = self.env['stock.picking'].browse(picking_to_validate).with_context(skip_expired=True) + return picking_to_validate.button_validate() + return True + + def process_no_expired(self): + """ Don't process for concerned pickings (ones with expired lots), but + process for all other pickings (in case of multi). """ + # Remove `self.pick_ids` from `button_validate_picking_ids` and call + # `button_validate` with the subset (if any). + pickings_to_validate = self.env['stock.picking'].browse(self.env.context.get('button_validate_picking_ids')) + pickings_to_validate = pickings_to_validate - self.picking_ids + if pickings_to_validate: + return pickings_to_validate.with_context(skip_expired=True).button_validate() + return True diff --git a/addons/product_expiry/wizard/confirm_expiry_view.xml b/addons/product_expiry/wizard/confirm_expiry_view.xml new file mode 100644 index 00000000000..d43c4255416 --- /dev/null +++ b/addons/product_expiry/wizard/confirm_expiry_view.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="confirm_expiry_view" model="ir.ui.view"> + <field name="name">Confirm</field> + <field name="model">expiry.picking.confirmation</field> + <field name="arch" type="xml"> + <form string="Confirmation"> + <p> + <field name="description"/> + </p> + <field name="show_lots" invisible="1"/> + <field name="lot_ids" attrs="{'invisible': [('show_lots', '=', False)]}"> + <tree string="Expired Lot(s)"> + <field name="product_id"/> + <field name="name"/> + </tree> + </field> + <footer> + <button name="process" + string="Confirm" + type="object" + class="btn-primary"/> + <button name="process_no_expired" + string="Proceed except for expired one" + type="object" + class="btn-secondary"/> + <button string="Discard" + class="btn-secondary" + special="cancel"/> + </footer> + </form> + </field> + </record> +</odoo> |
