diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/stock/tests/test_report.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/stock/tests/test_report.py')
| -rw-r--r-- | addons/stock/tests/test_report.py | 1043 |
1 files changed, 1043 insertions, 0 deletions
diff --git a/addons/stock/tests/test_report.py b/addons/stock/tests/test_report.py new file mode 100644 index 00000000..91a3b83c --- /dev/null +++ b/addons/stock/tests/test_report.py @@ -0,0 +1,1043 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from datetime import date, datetime, timedelta + +from odoo.tests.common import Form, SavepointCase + + +class TestReportsCommon(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner = cls.env['res.partner'].create({'name': 'Partner'}) + cls.ModelDataObj = cls.env['ir.model.data'] + cls.picking_type_in = cls.env['stock.picking.type'].browse(cls.ModelDataObj.xmlid_to_res_id('stock.picking_type_in')) + cls.picking_type_out = cls.env['stock.picking.type'].browse(cls.ModelDataObj.xmlid_to_res_id('stock.picking_type_out')) + cls.supplier_location = cls.env['stock.location'].browse(cls.ModelDataObj.xmlid_to_res_id('stock.stock_location_suppliers')) + cls.stock_location = cls.env['stock.location'].browse(cls.ModelDataObj.xmlid_to_res_id('stock.stock_location_stock')) + + product_form = Form(cls.env['product.product']) + product_form.type = 'product' + product_form.name = 'Product' + cls.product = product_form.save() + cls.product_template = cls.product.product_tmpl_id + + def get_report_forecast(self, product_template_ids=False, product_variant_ids=False, context=False): + if product_template_ids: + report = self.env['report.stock.report_product_template_replenishment'] + product_ids = product_template_ids + elif product_variant_ids: + report = self.env['report.stock.report_product_product_replenishment'] + product_ids = product_template_ids + if context: + report = report.with_context(context) + report_values = report._get_report_values(docids=product_ids) + docs = report_values['docs'] + lines = docs['lines'] + return report_values, docs, lines + + +class TestReports(TestReportsCommon): + def test_reports(self): + product1 = self.env['product.product'].create({ + 'name': 'Mellohi', + 'default_code': 'C418', + 'type': 'product', + 'categ_id': self.env.ref('product.product_category_all').id, + 'tracking': 'lot', + 'barcode': 'scan_me' + }) + lot1 = self.env['stock.production.lot'].create({ + 'name': 'Volume-Beta', + 'product_id': product1.id, + 'company_id': self.env.company.id, + }) + report = self.env.ref('stock.label_lot_template') + target = b'\n\n\n^XA\n^FO100,50\n^A0N,44,33^FD[C418]Mellohi^FS\n^FO100,100\n^A0N,44,33^FDLN/SN:Volume-Beta^FS\n^FO100,150^BY3\n^BCN,100,Y,N,N\n^FDVolume-Beta^FS\n^XZ\n\n\n' + + rendering, qweb_type = report._render_qweb_text(lot1.id) + self.assertEqual(target, rendering.replace(b' ', b''), 'The rendering is not good') + self.assertEqual(qweb_type, 'text', 'the report type is not good') + + def test_report_quantity_1(self): + product_form = Form(self.env['product.product']) + product_form.type = 'product' + product_form.name = 'Product' + product = product_form.save() + + warehouse = self.env['stock.warehouse'].search([], limit=1) + stock = self.env['stock.location'].create({ + 'name': 'New Stock', + 'usage': 'internal', + 'location_id': warehouse.view_location_id.id, + }) + + # Inventory Adjustement of 50.0 today. + self.env['stock.quant'].with_context(inventory_mode=True).create({ + 'product_id': product.id, + 'location_id': stock.id, + 'inventory_quantity': 50 + }) + self.env['stock.move'].flush() + report_records_today = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today())], + ['product_qty'], [], lazy=False) + report_records_tomorrow = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today() + timedelta(days=1))], + ['product_qty'], []) + report_records_yesterday = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today() - timedelta(days=1))], + ['product_qty'], []) + self.assertEqual(sum([r['product_qty'] for r in report_records_today]), 50.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_tomorrow]), 50.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_yesterday]), 0.0) + + # Delivery of 20.0 units tomorrow + move_out = self.env['stock.move'].create({ + 'name': 'Move Out 20', + 'date': datetime.now() + timedelta(days=1), + 'location_id': stock.id, + 'location_dest_id': self.env.ref('stock.stock_location_customers').id, + 'product_id': product.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': 20.0, + }) + self.env['stock.move'].flush() + report_records_tomorrow = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today() + timedelta(days=1))], + ['product_qty'], []) + self.assertEqual(sum([r['product_qty'] for r in report_records_tomorrow]), 50.0) + move_out._action_confirm() + self.env['stock.move'].flush() + report_records_tomorrow = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today() + timedelta(days=1))], + ['product_qty', 'state'], ['state'], lazy=False) + self.assertEqual(sum([r['product_qty'] for r in report_records_tomorrow if r['state'] == 'forecast']), 30.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_tomorrow if r['state'] == 'out']), -20.0) + report_records_today = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today())], + ['product_qty', 'state'], ['state'], lazy=False) + self.assertEqual(sum([r['product_qty'] for r in report_records_today if r['state'] == 'forecast']), 50.0) + + # Receipt of 10.0 units tomorrow + move_in = self.env['stock.move'].create({ + 'name': 'Move In 10', + 'date': datetime.now() + timedelta(days=1), + 'location_id': self.env.ref('stock.stock_location_suppliers').id, + 'location_dest_id': stock.id, + 'product_id': product.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': 10.0, + }) + move_in._action_confirm() + self.env['stock.move'].flush() + report_records_tomorrow = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today() + timedelta(days=1))], + ['product_qty', 'state'], ['state'], lazy=False) + self.assertEqual(sum([r['product_qty'] for r in report_records_tomorrow if r['state'] == 'forecast']), 40.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_tomorrow if r['state'] == 'out']), -20.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_tomorrow if r['state'] == 'in']), 10.0) + report_records_today = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today())], + ['product_qty', 'state'], ['state'], lazy=False) + self.assertEqual(sum([r['product_qty'] for r in report_records_today if r['state'] == 'forecast']), 50.0) + + # Delivery of 20.0 units tomorrow + move_out = self.env['stock.move'].create({ + 'name': 'Move Out 30 - Day-1', + 'date': datetime.now() - timedelta(days=1), + 'location_id': stock.id, + 'location_dest_id': self.env.ref('stock.stock_location_customers').id, + 'product_id': product.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': 30.0, + }) + move_out._action_confirm() + self.env['stock.move'].flush() + report_records_today = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today())], + ['product_qty', 'state'], ['state'], lazy=False) + report_records_tomorrow = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today() + timedelta(days=1))], + ['product_qty', 'state'], ['state'], lazy=False) + report_records_yesterday = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today() - timedelta(days=1))], + ['product_qty', 'state'], ['state'], lazy=False) + + self.assertEqual(sum([r['product_qty'] for r in report_records_yesterday if r['state'] == 'forecast']), -30.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_yesterday if r['state'] == 'out']), -30.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_yesterday if r['state'] == 'in']), 0.0) + + self.assertEqual(sum([r['product_qty'] for r in report_records_today if r['state'] == 'forecast']), 20.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_today if r['state'] == 'out']), 0.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_today if r['state'] == 'in']), 0.0) + + self.assertEqual(sum([r['product_qty'] for r in report_records_tomorrow if r['state'] == 'forecast']), 10.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_tomorrow if r['state'] == 'out']), -20.0) + self.assertEqual(sum([r['product_qty'] for r in report_records_tomorrow if r['state'] == 'in']), 10.0) + + def test_report_quantity_2(self): + """ Not supported case. + """ + product_form = Form(self.env['product.product']) + product_form.type = 'product' + product_form.name = 'Product' + product = product_form.save() + + warehouse = self.env['stock.warehouse'].search([], limit=1) + stock = self.env['stock.location'].create({ + 'name': 'Stock Under Warehouse', + 'usage': 'internal', + 'location_id': warehouse.view_location_id.id, + }) + stock_without_wh = self.env['stock.location'].create({ + 'name': 'Stock Outside Warehouse', + 'usage': 'internal', + 'location_id': self.env.ref('stock.stock_location_locations').id, + }) + self.env['stock.quant'].with_context(inventory_mode=True).create({ + 'product_id': product.id, + 'location_id': stock.id, + 'inventory_quantity': 50 + }) + self.env['stock.quant'].with_context(inventory_mode=True).create({ + 'product_id': product.id, + 'location_id': stock_without_wh.id, + 'inventory_quantity': 50 + }) + move = self.env['stock.move'].create({ + 'name': 'Move outside warehouse', + 'location_id': stock.id, + 'location_dest_id': stock_without_wh.id, + 'product_id': product.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': 10.0, + }) + move._action_confirm() + self.env['stock.move'].flush() + report_records = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today()), ('warehouse_id', '!=', False)], + ['product_qty', 'state'], ['state'], lazy=False) + self.assertEqual(sum([r['product_qty'] for r in report_records if r['state'] == 'forecast']), 40.0) + report_records = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today())], + ['product_qty', 'state'], ['state'], lazy=False) + self.assertEqual(sum([r['product_qty'] for r in report_records if r['state'] == 'forecast']), 40.0) + move = self.env['stock.move'].create({ + 'name': 'Move outside warehouse', + 'location_id': stock_without_wh.id, + 'location_dest_id': self.env.ref('stock.stock_location_customers').id, + 'product_id': product.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': 10.0, + }) + move._action_confirm() + self.env['stock.move'].flush() + report_records = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today())], + ['product_qty', 'state'], ['state'], lazy=False) + self.assertEqual(sum([r['product_qty'] for r in report_records if r['state'] == 'forecast']), 40.0) + + def test_report_quantity_3(self): + product_form = Form(self.env['product.product']) + product_form.type = 'product' + product_form.name = 'Product' + product = product_form.save() + + warehouse = self.env['stock.warehouse'].search([], limit=1) + stock = self.env['stock.location'].create({ + 'name': 'Rack', + 'usage': 'view', + 'location_id': warehouse.view_location_id.id, + }) + stock_real_loc = self.env['stock.location'].create({ + 'name': 'Drawer', + 'usage': 'internal', + 'location_id': stock.id, + }) + + self.env['stock.move'].flush() + report_records = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today())], + ['product_qty'], [], lazy=False) + self.assertEqual(sum([r['product_qty'] for r in report_records if r['product_qty']]), 0.0) + + # Receipt of 20.0 units tomorrow + move_in = self.env['stock.move'].create({ + 'name': 'Move In 20', + 'location_id': self.env.ref('stock.stock_location_suppliers').id, + 'location_dest_id': stock.id, + 'product_id': product.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': 20.0, + }) + move_in._action_confirm() + move_in.move_line_ids.location_dest_id = stock_real_loc.id + move_in.move_line_ids.qty_done = 20.0 + move_in._action_done() + self.env['stock.move'].flush() + report_records = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today())], + ['product_qty'], [], lazy=False) + self.assertEqual(sum([r['product_qty'] for r in report_records]), 20.0) + + # Delivery of 10.0 units tomorrow + move_out = self.env['stock.move'].create({ + 'name': 'Move Out 10', + 'location_id': stock.id, + 'location_dest_id': self.env.ref('stock.stock_location_customers').id, + 'product_id': product.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': 10.0, + }) + move_out._action_confirm() + move_out._action_assign() + move_out.move_line_ids.qty_done = 10.0 + move_out._action_done() + self.env['stock.move'].flush() + report_records = self.env['report.stock.quantity'].read_group( + [('product_id', '=', product.id), ('date', '=', date.today())], + ['product_qty'], [], lazy=False) + self.assertEqual(sum([r['product_qty'] for r in report_records]), 10.0) + + def test_report_forecast_1(self): + """ Checks report data for product is empty. Then creates and process + some operations and checks the report data accords rigthly these operations. + """ + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 0, "Must have 0 line.") + self.assertEqual(draft_picking_qty['in'], 0) + self.assertEqual(draft_picking_qty['out'], 0) + + # Creates a receipt then checks draft picking quantities. + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + receipt = receipt_form.save() + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 2 + receipt = receipt_form.save() + + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 0, "Must have 0 line.") + self.assertEqual(draft_picking_qty['in'], 2) + self.assertEqual(draft_picking_qty['out'], 0) + + # Creates a delivery then checks draft picking quantities. + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + delivery = delivery_form.save() + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + delivery = delivery_form.save() + + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 0, "Must have 0 line.") + self.assertEqual(draft_picking_qty['in'], 2) + self.assertEqual(draft_picking_qty['out'], 5) + + # Confirms the delivery: must have one report line and no more pending qty out now. + delivery.action_confirm() + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 1, "Must have 1 line.") + self.assertEqual(draft_picking_qty['in'], 2) + self.assertEqual(draft_picking_qty['out'], 0) + delivery_line = lines[0] + self.assertEqual(delivery_line['quantity'], 5) + self.assertEqual(delivery_line['replenishment_filled'], False) + self.assertEqual(delivery_line['document_out'].id, delivery.id) + + # Confirms the receipt, must have two report lines now: + # - line with 2 qty (from the receipt to the delivery) + # - line with 3 qty (delivery, unavailable) + receipt.action_confirm() + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 2, "Must have 2 line.") + self.assertEqual(draft_picking_qty['in'], 0) + self.assertEqual(draft_picking_qty['out'], 0) + fulfilled_line = lines[0] + unavailable_line = lines[1] + self.assertEqual(fulfilled_line['replenishment_filled'], True) + self.assertEqual(fulfilled_line['quantity'], 2) + self.assertEqual(fulfilled_line['document_in'].id, receipt.id) + self.assertEqual(fulfilled_line['document_out'].id, delivery.id) + self.assertEqual(unavailable_line['replenishment_filled'], False) + self.assertEqual(unavailable_line['quantity'], 3) + self.assertEqual(unavailable_line['document_out'].id, delivery.id) + + # Creates a new receipt for the remaining quantity, confirm it... + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 3 + receipt2 = receipt_form.save() + receipt2.action_confirm() + + # ... and valid the first one. + receipt_form = Form(receipt) + with receipt_form.move_ids_without_package.edit(0) as move_line: + move_line.quantity_done = 2 + receipt = receipt_form.save() + receipt.button_validate() + + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 2, "Still must have 2 line.") + self.assertEqual(draft_picking_qty['in'], 0) + self.assertEqual(draft_picking_qty['out'], 0) + line1 = lines[0] + line2 = lines[1] + # First line must be fulfilled thanks to the stock on hand. + self.assertEqual(line1['quantity'], 2) + self.assertEqual(line1['replenishment_filled'], True) + self.assertEqual(line1['document_in'], False) + self.assertEqual(line1['document_out'].id, delivery.id) + # Second line must be linked to the second receipt. + self.assertEqual(line2['quantity'], 3) + self.assertEqual(line2['replenishment_filled'], True) + self.assertEqual(line2['document_in'].id, receipt2.id) + self.assertEqual(line2['document_out'].id, delivery.id) + + def test_report_forecast_2_replenishments_order(self): + """ Creates a receipt then creates a delivery using half of the receipt quantity. + Checks replenishment lines are correctly sorted (assigned first, unassigned at the end). + """ + # Creates a receipt then checks draft picking quantities. + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 6 + receipt = receipt_form.save() + receipt.action_confirm() + + # Creates a delivery then checks draft picking quantities. + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 3 + delivery = delivery_form.save() + delivery.action_confirm() + + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + self.assertEqual(len(lines), 2, "Must have 2 line.") + line_1 = lines[0] + line_2 = lines[1] + self.assertEqual(line_1['document_in'].id, receipt.id) + self.assertEqual(line_1['document_out'].id, delivery.id) + self.assertEqual(line_2['document_in'].id, receipt.id) + self.assertEqual(line_2['document_out'], False) + + def test_report_forecast_3_sort_by_date(self): + """ Creates some deliveries with different dates and checks the report + lines are correctly sorted by date. Then, creates some receipts and + check their are correctly linked according to their date. + """ + today = datetime.today() + one_hours = timedelta(hours=1) + one_day = timedelta(days=1) + one_month = timedelta(days=30) + # Creates a bunch of deliveries with different date. + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + delivery_form.scheduled_date = today + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + delivery_1 = delivery_form.save() + delivery_1.action_confirm() + + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + delivery_form.scheduled_date = today + one_hours + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + delivery_2 = delivery_form.save() + delivery_2.action_confirm() + + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + delivery_form.scheduled_date = today - one_hours + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + delivery_3 = delivery_form.save() + delivery_3.action_confirm() + + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + delivery_form.scheduled_date = today + one_day + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + delivery_4 = delivery_form.save() + delivery_4.action_confirm() + + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + delivery_form.scheduled_date = today - one_day + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + delivery_5 = delivery_form.save() + delivery_5.action_confirm() + + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + delivery_form.scheduled_date = today + one_month + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + delivery_6 = delivery_form.save() + delivery_6.action_confirm() + + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + delivery_form.scheduled_date = today - one_month + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + delivery_7 = delivery_form.save() + delivery_7.action_confirm() + + # Order must be: 7, 5, 3, 1, 2, 4, 6 + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 7, "The report must have 7 line.") + self.assertEqual(draft_picking_qty['in'], 0) + self.assertEqual(draft_picking_qty['out'], 0) + self.assertEqual(lines[0]['document_out'].id, delivery_7.id) + self.assertEqual(lines[1]['document_out'].id, delivery_5.id) + self.assertEqual(lines[2]['document_out'].id, delivery_3.id) + self.assertEqual(lines[3]['document_out'].id, delivery_1.id) + self.assertEqual(lines[4]['document_out'].id, delivery_2.id) + self.assertEqual(lines[5]['document_out'].id, delivery_4.id) + self.assertEqual(lines[6]['document_out'].id, delivery_6.id) + + # Creates 3 receipts for 20 units. + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + receipt_form.scheduled_date = today + one_month + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + receipt_1 = receipt_form.save() + receipt_1.action_confirm() + + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + receipt_form.scheduled_date = today - one_month + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + receipt_2 = receipt_form.save() + receipt_2.action_confirm() + + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + receipt_form.scheduled_date = today - one_hours + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 10 + receipt_3 = receipt_form.save() + receipt_3.action_confirm() + + # Check report lines (link and order). + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 7, "The report must have 7 line.") + self.assertEqual(draft_picking_qty['in'], 0) + self.assertEqual(draft_picking_qty['out'], 0) + self.assertEqual(lines[0]['document_out'].id, delivery_7.id) + self.assertEqual(lines[0]['document_in'].id, receipt_2.id) + self.assertEqual(lines[0]['is_late'], False) + self.assertEqual(lines[1]['document_out'].id, delivery_5.id) + self.assertEqual(lines[1]['document_in'].id, receipt_3.id) + self.assertEqual(lines[1]['is_late'], True) + self.assertEqual(lines[2]['document_out'].id, delivery_3.id) + self.assertEqual(lines[2]['document_in'].id, receipt_3.id) + self.assertEqual(lines[2]['is_late'], False) + self.assertEqual(lines[3]['document_out'].id, delivery_1.id) + self.assertEqual(lines[3]['document_in'].id, receipt_1.id) + self.assertEqual(lines[3]['is_late'], True) + self.assertEqual(lines[4]['document_out'].id, delivery_2.id) + self.assertEqual(lines[4]['document_in'], False) + self.assertEqual(lines[5]['document_out'].id, delivery_4.id) + self.assertEqual(lines[5]['document_in'], False) + self.assertEqual(lines[6]['document_out'].id, delivery_6.id) + self.assertEqual(lines[6]['document_in'], False) + + def test_report_forecast_4_intermediate_transfers(self): + """ Create a receipt in 3 steps and check the report line. + """ + grp_multi_loc = self.env.ref('stock.group_stock_multi_locations') + grp_multi_routes = self.env.ref('stock.group_adv_location') + self.env.user.write({'groups_id': [(4, grp_multi_loc.id)]}) + self.env.user.write({'groups_id': [(4, grp_multi_routes.id)]}) + # Warehouse config. + warehouse = self.env.ref('stock.warehouse0') + warehouse.reception_steps = 'three_steps' + # Product config. + self.product.write({'route_ids': [(4, self.env.ref('stock.route_warehouse0_mto').id)]}) + # Create a RR + pg1 = self.env['procurement.group'].create({}) + reordering_rule = self.env['stock.warehouse.orderpoint'].create({ + 'name': 'Product RR', + 'location_id': warehouse.lot_stock_id.id, + 'product_id': self.product.id, + 'product_min_qty': 5, + 'product_max_qty': 10, + 'group_id': pg1.id, + }) + reordering_rule.action_replenish() + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + pickings = self.env['stock.picking'].search([('product_id', '=', self.product.id)]) + receipt = pickings.filtered(lambda p: p.picking_type_id.id == self.picking_type_in.id) + + # The Forecasted Report don't show intermediate moves, it must display only ingoing/outgoing documents. + self.assertEqual(len(lines), 1, "The report must have only 1 line.") + self.assertEqual(lines[0]['document_in'].id, receipt.id, "The report must only show the receipt.") + self.assertEqual(lines[0]['document_out'], False) + self.assertEqual(lines[0]['quantity'], reordering_rule.product_max_qty) + + def test_report_forecast_5_multi_warehouse(self): + """ Create some transfer for two different warehouses and check the + report display the good moves according to the selected warehouse. + """ + # Warehouse config. + wh_2 = self.env['stock.warehouse'].create({ + 'name': 'Evil Twin Warehouse', + 'code': 'ETWH', + }) + picking_type_out_2 = self.env['stock.picking.type'].search([ + ('code', '=', 'outgoing'), + ('warehouse_id', '=', wh_2.id), + ]) + + # Creates a delivery then checks draft picking quantities. + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + delivery = delivery_form.save() + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + delivery = delivery_form.save() + + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 0, "Must have 0 line.") + self.assertEqual(draft_picking_qty['out'], 5) + + report_values, docs, lines = self.get_report_forecast( + product_template_ids=self.product_template.ids, + context={'warehouse': wh_2.id}, + ) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 0) + self.assertEqual(draft_picking_qty['out'], 0) + + # Confirm the delivery -> The report must now have 1 line. + delivery.action_confirm() + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 1) + self.assertEqual(draft_picking_qty['out'], 0) + self.assertEqual(lines[0]['document_out'].id, delivery.id) + self.assertEqual(lines[0]['quantity'], 5) + + report_values, docs, lines = self.get_report_forecast( + product_template_ids=self.product_template.ids, + context={'warehouse': wh_2.id}, + ) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 0) + self.assertEqual(draft_picking_qty['out'], 0) + + # Creates a delivery for the second warehouse. + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = picking_type_out_2 + delivery_2 = delivery_form.save() + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 8 + delivery_2 = delivery_form.save() + + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 1) + self.assertEqual(draft_picking_qty['out'], 0) + self.assertEqual(lines[0]['document_out'].id, delivery.id) + self.assertEqual(lines[0]['quantity'], 5) + + report_values, docs, lines = self.get_report_forecast( + product_template_ids=self.product_template.ids, + context={'warehouse': wh_2.id}, + ) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 0) + self.assertEqual(draft_picking_qty['out'], 8) + # Confirm the second delivery -> The report must now have 1 line. + delivery_2.action_confirm() + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 1) + self.assertEqual(draft_picking_qty['out'], 0) + self.assertEqual(lines[0]['document_out'].id, delivery.id) + self.assertEqual(lines[0]['quantity'], 5) + + report_values, docs, lines = self.get_report_forecast( + product_template_ids=self.product_template.ids, + context={'warehouse': wh_2.id}, + ) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 1) + self.assertEqual(draft_picking_qty['out'], 0) + self.assertEqual(lines[0]['document_out'].id, delivery_2.id) + self.assertEqual(lines[0]['quantity'], 8) + + def test_report_forecast_6_multi_company(self): + """ Create transfers for two different companies and check report + display the right transfers. + """ + # Configure second warehouse. + company_2 = self.env['res.company'].create({'name': 'Aperture Science'}) + wh_2 = self.env['stock.warehouse'].search([('company_id', '=', company_2.id)]) + wh_2_picking_type_in = wh_2.in_type_id + + # Creates a receipt then checks draft picking quantities. + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + wh_1_receipt = receipt_form.save() + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 2 + wh_1_receipt = receipt_form.save() + + # Creates a receipt then checks draft picking quantities. + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = wh_2_picking_type_in + wh_2_receipt = receipt_form.save() + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 5 + wh_2_receipt = receipt_form.save() + + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 0, "Must have 0 line.") + self.assertEqual(draft_picking_qty['in'], 2) + self.assertEqual(draft_picking_qty['out'], 0) + + report_values, docs, lines = self.get_report_forecast( + product_template_ids=self.product_template.ids, + context={'warehouse': wh_2.id}, + ) + draft_picking_qty = docs['draft_picking_qty'] + self.assertEqual(len(lines), 0, "Must have 0 line.") + self.assertEqual(draft_picking_qty['in'], 5) + self.assertEqual(draft_picking_qty['out'], 0) + + # Confirm the receipts -> The report must now have one line for each company. + wh_1_receipt.action_confirm() + wh_2_receipt.action_confirm() + + report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + self.assertEqual(len(lines), 1, "Must have 1 line.") + self.assertEqual(lines[0]['document_in'].id, wh_1_receipt.id) + self.assertEqual(lines[0]['quantity'], 2) + + report_values, docs, lines = self.get_report_forecast( + product_template_ids=self.product_template.ids, + context={'warehouse': wh_2.id}, + ) + self.assertEqual(len(lines), 1, "Must have 1 line.") + self.assertEqual(lines[0]['document_in'].id, wh_2_receipt.id) + self.assertEqual(lines[0]['quantity'], 5) + + def test_report_forecast_7_multiple_variants(self): + """ Create receipts for different variant products and check the report + work well with them.Also, check the receipt/delivery lines are correctly + linked depending of their product variant. + """ + # Create some variant's attributes. + product_attr_color = self.env['product.attribute'].create({'name': 'Color'}) + color_gray = self.env['product.attribute.value'].create({ + 'name': 'Old Fashioned Gray', + 'attribute_id': product_attr_color.id, + }) + color_blue = self.env['product.attribute.value'].create({ + 'name': 'Electric Blue', + 'attribute_id': product_attr_color.id, + }) + product_attr_size = self.env['product.attribute'].create({'name': 'size'}) + size_pocket = self.env['product.attribute.value'].create({ + 'name': 'Pocket', + 'attribute_id': product_attr_size.id, + }) + size_xl = self.env['product.attribute.value'].create({ + 'name': 'XL', + 'attribute_id': product_attr_size.id, + }) + + # Create a new product and set some variants on the product. + product_template = self.env['product.template'].create({ + 'name': 'Game Joy', + 'type': 'product', + 'attribute_line_ids': [ + (0, 0, { + 'attribute_id': product_attr_color.id, + 'value_ids': [(6, 0, [color_gray.id, color_blue.id])] + }), + (0, 0, { + 'attribute_id': product_attr_size.id, + 'value_ids': [(6, 0, [size_pocket.id, size_xl.id])] + }), + ], + }) + gamejoy_pocket_gray = product_template.product_variant_ids[0] + gamejoy_xl_gray = product_template.product_variant_ids[1] + gamejoy_pocket_blue = product_template.product_variant_ids[2] + gamejoy_xl_blue = product_template.product_variant_ids[3] + + # Create two receipts. + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = gamejoy_pocket_gray + move_line.product_uom_qty = 8 + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = gamejoy_pocket_blue + move_line.product_uom_qty = 4 + receipt_1 = receipt_form.save() + receipt_1.action_confirm() + + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = gamejoy_pocket_gray + move_line.product_uom_qty = 2 + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = gamejoy_xl_gray + move_line.product_uom_qty = 10 + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = gamejoy_xl_blue + move_line.product_uom_qty = 12 + receipt_2 = receipt_form.save() + receipt_2.action_confirm() + + report_values, docs, lines = self.get_report_forecast(product_template_ids=product_template.ids) + self.assertEqual(len(lines), 5, "Must have 5 lines.") + self.assertEqual(docs['product_variants'].ids, product_template.product_variant_ids.ids) + + # Create a delivery for one of these products and check the report lines + # are correctly linked to the good receipts. + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = gamejoy_pocket_gray + move_line.product_uom_qty = 10 + delivery = delivery_form.save() + delivery.action_confirm() + + report_values, docs, lines = self.get_report_forecast(product_template_ids=product_template.ids) + self.assertEqual(len(lines), 5, "Still must have 5 lines.") + self.assertEqual(docs['product_variants'].ids, product_template.product_variant_ids.ids) + # First and second lines should be about the "Game Joy Pocket (gray)" + # and must link the delivery with the two receipt lines. + line_1 = lines[0] + line_2 = lines[1] + self.assertEqual(line_1['product']['id'], gamejoy_pocket_gray.id) + self.assertEqual(line_1['quantity'], 8) + self.assertTrue(line_1['replenishment_filled']) + self.assertEqual(line_1['document_in'].id, receipt_1.id) + self.assertEqual(line_1['document_out'].id, delivery.id) + self.assertEqual(line_2['product']['id'], gamejoy_pocket_gray.id) + self.assertEqual(line_2['quantity'], 2) + self.assertTrue(line_2['replenishment_filled']) + self.assertEqual(line_2['document_in'].id, receipt_2.id) + self.assertEqual(line_2['document_out'].id, delivery.id) + + def test_report_forecast_8_delivery_to_receipt_link(self): + """ + Create 2 deliveries, and 1 receipt tied to the second delivery. + The report should show the source document as the 2nd delivery, and show the first + delivery completely unfilled. + """ + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 100 + delivery = delivery_form.save() + delivery.action_confirm() + + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 200 + delivery2 = delivery_form.save() + delivery2.action_confirm() + + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + receipt = receipt_form.save() + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 200 + receipt = receipt_form.save() + receipt.move_lines[0].write({ + 'move_dest_ids': [(4, delivery2.move_lines[0].id)], + }) + receipt.action_confirm() + self.env['base'].flush() + + _, _, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + + self.assertEqual(len(lines), 2, 'Only 2 lines') + delivery_line = [l for l in lines if l['document_out'].id == delivery.id][0] + self.assertTrue(delivery_line, 'No line for delivery 1') + self.assertFalse(delivery_line['replenishment_filled']) + delivery2_line = [l for l in lines if l['document_out'].id == delivery2.id][0] + self.assertTrue(delivery2_line, 'No line for delivery 2') + self.assertTrue(delivery2_line['replenishment_filled']) + + def test_report_forecast_9_delivery_to_receipt_link_over_received(self): + """ + Create 2 deliveries, and 1 receipt tied to the second delivery. + Set the quantity on the receipt to be enough for BOTH deliveries. + For example, this can happen if they have manually increased the quantity on the generated PO. + The report should show both deliveries fulfilled. + """ + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 100 + delivery = delivery_form.save() + delivery.action_confirm() + + delivery_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + delivery_form.partner_id = self.partner + delivery_form.picking_type_id = self.picking_type_out + with delivery_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 200 + delivery2 = delivery_form.save() + delivery2.action_confirm() + + receipt_form = Form(self.env['stock.picking'].with_context( + force_detailed_view=True + ), view='stock.view_picking_form') + receipt_form.partner_id = self.partner + receipt_form.picking_type_id = self.picking_type_in + receipt = receipt_form.save() + with receipt_form.move_ids_without_package.new() as move_line: + move_line.product_id = self.product + move_line.product_uom_qty = 300 + receipt = receipt_form.save() + receipt.move_lines[0].write({ + 'move_dest_ids': [(4, delivery2.move_lines[0].id)], + }) + receipt.action_confirm() + self.env['base'].flush() + + _, _, lines = self.get_report_forecast(product_template_ids=self.product_template.ids) + + self.assertEqual(len(lines), 2, 'Only 2 lines') + delivery_line = [l for l in lines if l['document_out'].id == delivery.id][0] + self.assertTrue(delivery_line, 'No line for delivery 1') + self.assertTrue(delivery_line['replenishment_filled']) + delivery2_line = [l for l in lines if l['document_out'].id == delivery2.id][0] + self.assertTrue(delivery2_line, 'No line for delivery 2') + self.assertTrue(delivery2_line['replenishment_filled']) |
