diff options
| author | Odoo's Mergebot <xmo+roboto@odoo.com> | 2020-07-03 17:34:51 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-07-03 17:34:51 +0200 |
| commit | 9d7f3be8b3449d2809f6742b358e9da5bef53797 (patch) | |
| tree | cf93bf9424fa8f93898f931d3ac3c051e6901371 /addons | |
| parent | 600bfa11963e98a02cbec8a2273a27dcbd91be7a (diff) | |
| parent | 0aa0984f832209bee30de22c0550d76a46ddc05f (diff) | |
[IMP] delivery, product_expiry, stock: improve delivery slip
Improve delivery slip with following features:
1. Group Kit components on the delivery slip
- Split kit components by kit "sections"
2. Print one line per product whatever the tracking
- Combine duplicate product move lines when "Display Lots & Serial Numbers on Delivery Slips" is not activated or no lots/serial numbers are assigned in the picking
3. Group packages on the delivery slip
- Split products up by package "sections"
- Print package info such as "weight"
4. Expiration Dates
- Give option to print lot/serial number expiration dates on delivery slip
Task: 2008609
Description of the issue/feature this PR addresses:
Current behavior before PR:
Desired behavior after PR is merged:
--
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr
closes odoo/odoo#49832
Signed-off-by: Arnold Moyaux <amoyaux@users.noreply.github.com>
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/delivery/models/stock_move.py | 14 | ||||
| -rw-r--r-- | addons/delivery/models/stock_picking.py | 8 | ||||
| -rw-r--r-- | addons/delivery/views/report_deliveryslip.xml | 47 | ||||
| -rw-r--r-- | addons/mrp/__manifest__.py | 1 | ||||
| -rw-r--r-- | addons/mrp/models/stock_move.py | 18 | ||||
| -rw-r--r-- | addons/mrp/report/report_deliveryslip.xml | 81 | ||||
| -rw-r--r-- | addons/product_expiry/__manifest__.py | 3 | ||||
| -rw-r--r-- | addons/product_expiry/models/__init__.py | 1 | ||||
| -rw-r--r-- | addons/product_expiry/models/res_config_settings.py | 21 | ||||
| -rw-r--r-- | addons/product_expiry/report/report_deliveryslip.xml | 22 | ||||
| -rw-r--r-- | addons/product_expiry/security/stock_security.xml | 9 | ||||
| -rw-r--r-- | addons/product_expiry/views/product_template_views.xml | 2 | ||||
| -rw-r--r-- | addons/product_expiry/views/res_config_settings_views.xml | 23 | ||||
| -rw-r--r-- | addons/sale/views/sale_views.xml | 3 | ||||
| -rw-r--r-- | addons/sale_stock/views/sale_order_views.xml | 5 | ||||
| -rw-r--r-- | addons/stock/models/stock_move_line.py | 29 | ||||
| -rw-r--r-- | addons/stock/report/report_deliveryslip.xml | 134 |
17 files changed, 386 insertions, 35 deletions
diff --git a/addons/delivery/models/stock_move.py b/addons/delivery/models/stock_move.py index 49ace8ec7ef..3b51fe7763e 100644 --- a/addons/delivery/models/stock_move.py +++ b/addons/delivery/models/stock_move.py @@ -41,3 +41,17 @@ class StockMoveLine(models.Model): unit_price = move_line.product_id.list_price qty = move_line.product_uom_id._compute_quantity(move_line.qty_done, move_line.product_id.uom_id) move_line.sale_price = unit_price * qty + + def _get_aggregated_product_quantities(self, **kwargs): + """Returns dictionary of products and corresponding values of interest + hs_code + + Unfortunately because we are working with aggregated data, we have to loop through the + aggregation to add more values to each datum. This extension adds on the hs_code value. + + returns: dictionary {same_key_as_super: {same_values_as_super, hs_code}, ...} + """ + aggregated_move_lines = super()._get_aggregated_product_quantities(**kwargs) + for aggregated_move_line in aggregated_move_lines: + hs_code = aggregated_move_lines[aggregated_move_line]['product'].product_tmpl_id.hs_code + aggregated_move_lines[aggregated_move_line]['hs_code'] = hs_code + return aggregated_move_lines diff --git a/addons/delivery/models/stock_picking.py b/addons/delivery/models/stock_picking.py index d4c90d77d55..20ad15160f2 100644 --- a/addons/delivery/models/stock_picking.py +++ b/addons/delivery/models/stock_picking.py @@ -66,7 +66,8 @@ class StockPicking(models.Model): @api.depends('move_line_ids.result_package_id', 'move_line_ids.result_package_id.shipping_weight', 'weight_bulk') def _compute_shipping_weight(self): for picking in self: - picking.shipping_weight = picking.weight_bulk + sum([pack.shipping_weight for pack in picking.package_ids]) + # if shipping weight is not assigned => default to calculated product weight + picking.shipping_weight = picking.weight_bulk + sum([pack.shipping_weight or pack.weight for pack in picking.package_ids]) def _get_default_weight_uom(self): return self.env['product.template']._get_weight_uom_name_from_ir_config_parameter() @@ -83,8 +84,9 @@ class StockPicking(models.Model): carrier_tracking_url = fields.Char(string='Tracking URL', compute='_compute_carrier_tracking_url') weight_uom_name = fields.Char(string='Weight unit of measure label', compute='_compute_weight_uom_name', readonly=True, default=_get_default_weight_uom) package_ids = fields.Many2many('stock.quant.package', compute='_compute_packages', string='Packages') - weight_bulk = fields.Float('Bulk Weight', compute='_compute_bulk_weight') - shipping_weight = fields.Float("Weight for Shipping", compute='_compute_shipping_weight', help="Total weight of the packages and products which are not in a package. That's the weight used to compute the cost of the shipping.") + weight_bulk = fields.Float('Bulk Weight', compute='_compute_bulk_weight', help="Total weight of products which are not in a package.") + shipping_weight = fields.Float("Weight for Shipping", compute='_compute_shipping_weight', + help="Total weight of packages and products not in a package. Packages with no shipping weight specified will default to their products' total weight. This is the weight used to compute the cost of the shipping.") is_return_picking = fields.Boolean(compute='_compute_return_picking') return_label_ids = fields.One2many('ir.attachment', compute='_compute_return_label') diff --git a/addons/delivery/views/report_deliveryslip.xml b/addons/delivery/views/report_deliveryslip.xml index 43031e5803e..4bf0057dd3b 100644 --- a/addons/delivery/views/report_deliveryslip.xml +++ b/addons/delivery/views/report_deliveryslip.xml @@ -6,7 +6,7 @@ <p t-field="o.carrier_id"/> </div> <div t-if="o.shipping_weight" class="col-auto"> - <strong>Weight:</strong> + <strong>Total Weight:</strong> <br/> <span t-field="o.shipping_weight"/> <span t-field="o.weight_uom_name"/> @@ -22,16 +22,51 @@ <th t-if="has_hs_code"><strong>HS Code</strong></th> </xpath> - <xpath expr="//table[@name='stock_move_table']/tbody/tr" position="inside"> - <td t-if="has_hs_code"><span t-field="move.product_id.hs_code"/></td> - </xpath> - <xpath expr="//table[@name='stock_move_line_table']/thead/tr" position="inside"> <th t-if="has_hs_code"><strong>HS Code</strong></th> </xpath> - <xpath expr="//table[@name='stock_move_line_table']/tbody/tr" position="inside"> + </template> + + <!-- HS Code to table rows--> + <template id="stock_report_delivery_has_serial_move_line_inherit_delivery" inherit_id="stock.stock_report_delivery_has_serial_move_line"> + <xpath expr="//td[@name='move_line_lot_qty_done']" position="after"> <td t-if="has_hs_code"><span t-field="move_line.product_id.hs_code"/></td> </xpath> + </template> + <template id="stock_report_delivery_aggregated_move_lines_inherit_delivery" inherit_id="stock.stock_report_delivery_aggregated_move_lines"> + <xpath expr="//td[@name='move_line_aggregated_qty_done']" position="after"> + <td t-if="has_hs_code"><span t-esc="aggregated_lines[line]['hs_code']"/></td> + </xpath> + </template> + <!-- package related "section lines" --> + <template id="stock_report_delivery_package_section_line_inherit_delivery" inherit_id="stock.stock_report_delivery_package_section_line"> + <!-- Add additional Package section line info --> + <xpath expr="//td[@name='package_info']" position="inside"> + <t t-if="package.shipping_weight or package.weight"> + <!-- assume manually typed in value = priority --> + <t t-if="package.shipping_weight"> + <span> - Weight: </span> + <span t-field="package.shipping_weight"/> + <span t-field="package.weight_uom_name"/> + </t> + <!-- otherwise default to calculated value --> + <t t-else=""> + <span> - Weight (estimated): </span> + <span t-field="package.weight"/> + <span t-field="package.weight_uom_name"/> + </t> + </t> + </xpath> + </template> + <template id="delivery_stock_report_delivery_no_package_section_line" inherit_id="stock.stock_report_delivery_no_package_section_line"> + <!-- Add additional No Package section line info --> + <xpath expr="//td[@name='no_package_info']" position="inside"> + <t t-if="o.weight_bulk"> + <span> - Weight: </span> + <span t-field="o.weight_bulk"/> + <span t-field="o.weight_uom_name"/> + </t> + </xpath> </template> </odoo> diff --git a/addons/mrp/__manifest__.py b/addons/mrp/__manifest__.py index ce6d450810d..a5fdb6e3ebc 100644 --- a/addons/mrp/__manifest__.py +++ b/addons/mrp/__manifest__.py @@ -36,6 +36,7 @@ 'views/res_config_settings_views.xml', 'views/mrp_templates.xml', 'views/stock_scrap_views.xml', + 'report/report_deliveryslip.xml', 'report/mrp_report_views_main.xml', 'report/mrp_report_bom_structure.xml', 'report/mrp_production_templates.xml', diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 3ad3ba69067..f006fdb22d9 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -60,6 +60,24 @@ class StockMoveLine(models.Model): move_line._log_message(production, move_line, 'mrp.track_production_move_template', vals) return super(StockMoveLine, self).write(vals) + def _get_aggregated_product_quantities(self, **kwargs): + """Returns dictionary of products and corresponding values of interest grouped by optional kit_name + + Removes descriptions where description == kit_name. kit_name is expected to be passed as a + kwargs value because this is not directly stored in move_line_ids. Unfortunately because we + are working with aggregated data, we have to loop through the aggregation to do this removal. + + arguments: kit_name (optional): string value of a kit name passed as a kwarg + returns: dictionary {same_key_as_super: {same_values_as_super, ...} + """ + aggregated_move_lines = super()._get_aggregated_product_quantities(**kwargs) + kit_name = kwargs.get('kit_name') + if kit_name: + for aggregated_move_line in aggregated_move_lines: + if aggregated_move_lines[aggregated_move_line]['description'] == kit_name: + aggregated_move_lines[aggregated_move_line]['description'] = "" + return aggregated_move_lines + class StockMove(models.Model): _inherit = 'stock.move' diff --git a/addons/mrp/report/report_deliveryslip.xml b/addons/mrp/report/report_deliveryslip.xml new file mode 100644 index 00000000000..88370ca6e78 --- /dev/null +++ b/addons/mrp/report/report_deliveryslip.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <template id="stock_report_delivery_document_inherit_mrp" inherit_id="stock.report_delivery_document"> + <!-- needs to be set before so elif directly follows if later on --> + <xpath expr="//t[@name='has_packages']" position="before"> + <!-- get only the top level kits' (i.e. no subkit) move lines for easier mapping later on + we ignore subkit groupings--> + <!-- note that move.name uses top level kit's product.template.display_name value instead of product.template.name --> + <t t-set="has_kits" t-value="o.move_line_ids.filtered(lambda l: l.move_id.bom_line_id and l.move_id.bom_line_id.bom_id.product_tmpl_id.display_name == l.move_id.name)"/> + </xpath> + <xpath expr="//t[@name='no_package_section']" position="before"> + <t t-set="has_kits" t-value="move_lines.filtered(lambda l: l.move_id.bom_line_id and l.move_id.bom_line_id.bom_id.product_tmpl_id.display_name == l.move_id.name)"/> + <t t-if="has_kits"> + <!-- print the products not in a package or kit first --> + <t t-set="move_lines" t-value="move_lines.filtered(lambda m: not m.move_id.bom_line_id)"/> + </t> + </xpath> + <xpath expr="//t[@name='no_package_move_lines']" position="inside"> + <t t-call="mrp.stock_report_delivery_kit_sections"/> + </xpath> + <xpath expr="//t[@name='has_packages']" position="after"> + <!-- Additional use case: group by kits when no packages exist and then apply use case 1. (serial/lot numbers used/printed) --> + <t t-elif="has_kits and not has_packages"> + <t t-call="mrp.stock_report_delivery_kit_sections"/> + <t t-call="mrp.stock_report_delivery_no_kit_section"/> + </t> + </xpath> + </template> + + <template id="stock_report_delivery_kit_sections"> + <!-- do another map to get unique top level kits --> + <t t-set="kits" t-value="has_kits.mapped('move_id.bom_line_id.bom_id.product_tmpl_id')"/> + <t t-foreach="kits" t-as="kit"> + <tr t-att-class="'bg-200 font-weight-bold o_line_section'"> + <td colspan="99"> + <span t-esc="kit.display_name"/> + </td> + </tr> + <t t-set="kit_move_lines" t-value="has_kits.filtered(lambda l: l.move_id.name == kit.display_name)"/> + <t t-if="has_serial_number"> + <tr t-foreach="kit_move_lines" t-as="move_line"> + <t t-set="description" t-as="move_line.move_id.description_picking"/> + <t t-if="description == kit.name"> + <t t-set="description" t-value=""/> + </t> + <t t-call="stock.stock_report_delivery_has_serial_move_line"/> + </tr> + </t> + <t t-else=""> + <!-- move line description by default is the product_template.name (kit name), instead of display_name--> + <t t-set="aggregated_lines" t-value="kit_move_lines._get_aggregated_product_quantities(kit_name=kit.name)"/> + <t t-if="aggregated_lines"> + <t t-call="stock.stock_report_delivery_aggregated_move_lines"/> + </t> + </t> + </t> + </template> + + <!-- No kit section is expected to only be called in no packages case --> + <template id="stock_report_delivery_no_kit_section"> + <!-- Do another section for kit-less products if they exist --> + <t t-set="no_kit_move_lines" t-value="o.move_line_ids.filtered(lambda l: not l.move_id.bom_line_id)"/> + <t t-if="no_kit_move_lines"> + <tr t-att-class="'bg-200 font-weight-bold o_line_section'"> + <td colspan="99"> + <span>Products not associated with a kit</span> + </td> + </tr> + <t t-if="has_serial_number"> + <tr t-foreach="no_kit_move_lines" t-as="move_line"> + <t t-call="stock.stock_report_delivery_has_serial_move_line"/> + </tr> + </t> + <t t-else=""> + <t t-set="aggregated_lines" t-value="no_kit_move_lines._get_aggregated_product_quantities()"/> + <t t-if="aggregated_lines"> + <t t-call="stock.stock_report_delivery_aggregated_move_lines"/> + </t> + </t> + </t> + </template> +</odoo> diff --git a/addons/product_expiry/__manifest__.py b/addons/product_expiry/__manifest__.py index 2021381a583..0a620081fa8 100644 --- a/addons/product_expiry/__manifest__.py +++ b/addons/product_expiry/__manifest__.py @@ -18,11 +18,14 @@ Following dates can be tracked: Also implements the removal strategy First Expiry First Out (FEFO) widely used, for example, in food industries. """, 'data': ['security/ir.model.access.csv', + 'security/stock_security.xml', 'views/production_lot_views.xml', 'views/product_template_views.xml', + 'views/res_config_settings_views.xml', 'views/stock_move_views.xml', 'views/stock_quant_views.xml', 'wizard/confirm_expiry_view.xml', + 'report/report_deliveryslip.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 ce07f86f046..55fcdd6ea27 100644 --- a/addons/product_expiry/models/__init__.py +++ b/addons/product_expiry/models/__init__.py @@ -3,6 +3,7 @@ from . import production_lot from . import product_product +from . import res_config_settings from . import stock_move_line from . import stock_move from . import stock_picking diff --git a/addons/product_expiry/models/res_config_settings.py b/addons/product_expiry/models/res_config_settings.py new file mode 100644 index 00000000000..bb565c56346 --- /dev/null +++ b/addons/product_expiry/models/res_config_settings.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + group_expiry_date_on_delivery_slip = fields.Boolean("Display Expiration Dates on Delivery Slips", + implied_group='product_expiry.group_expiry_date_on_delivery_slip') + + @api.onchange('group_lot_on_delivery_slip') + def _onchange_group_lot_on_delivery_slip(self): + if not self.group_lot_on_delivery_slip: + self.group_expiry_date_on_delivery_slip = False + + @api.onchange('module_product_expiry') + def _onchange_module_product_expiry(self): + if not self.module_product_expiry: + self.group_expiry_date_on_delivery_slip = False diff --git a/addons/product_expiry/report/report_deliveryslip.xml b/addons/product_expiry/report/report_deliveryslip.xml new file mode 100644 index 00000000000..1d390ee181c --- /dev/null +++ b/addons/product_expiry/report/report_deliveryslip.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <template id="stock_report_delivery_document_inherit_product_expiry" inherit_id="stock.report_delivery_document"> + <xpath expr="//t[@name='lot_serial']" position="after"> + <t t-set="has_expiry_date" t-value="False"/> + <t t-set="has_expiry_date" + t-value="o.move_line_ids.filtered(lambda ml: ml.lot_id.expiration_date)" + groups="product_expiry.group_expiry_date_on_delivery_slip"/> + <t name="expiry_date" t-if="has_expiry_date"> + <th>Expiration Date</th> + </t> + </xpath> + </template> + + <template id="stock_report_delivery_has_serial_move_line_inherit_product_expiry" inherit_id="stock.stock_report_delivery_has_serial_move_line"> + <xpath expr="//t[@name='move_line_lot']" position="after"> + <t t-if="has_expiry_date"> + <td><span t-field="move_line.lot_id.expiration_date" t-options='{"widget": "date"}'/></td> + </t> + </xpath> + </template> +</odoo> diff --git a/addons/product_expiry/security/stock_security.xml b/addons/product_expiry/security/stock_security.xml new file mode 100644 index 00000000000..0757b8ac205 --- /dev/null +++ b/addons/product_expiry/security/stock_security.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> +<data noupdate="0"> + <record id="group_expiry_date_on_delivery_slip" model="res.groups"> + <field name="name">Include expiration dates on delivery slip</field> + <field name="category_id" ref="base.module_category_hidden"/> + </record> +</data> +</odoo> diff --git a/addons/product_expiry/views/product_template_views.xml b/addons/product_expiry/views/product_template_views.xml index c6af30107e6..a6deea0d936 100644 --- a/addons/product_expiry/views/product_template_views.xml +++ b/addons/product_expiry/views/product_template_views.xml @@ -8,7 +8,7 @@ <group name="traceability" position="inside"> <field name="use_expiration_date" attrs="{'invisible': [('tracking', '=','none')]}"/> </group> - <group name="group_lots_and_weight" position="after"> + <group name="stock_property" position="after"> <group string="Dates" name="expiry_and_lots" groups="stock.group_production_lot" attrs="{'invisible': ['|', ('tracking', '=','none'), ('use_expiration_date', '=', False)]}"> <label for="expiration_time"/> diff --git a/addons/product_expiry/views/res_config_settings_views.xml b/addons/product_expiry/views/res_config_settings_views.xml new file mode 100644 index 00000000000..1c636bed186 --- /dev/null +++ b/addons/product_expiry/views/res_config_settings_views.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="res_config_settings_view_form_stock" model="ir.ui.view"> + <field name="name">res.config.settings.view.form.inherit.product.expiry.stock</field> + <field name="model">res.config.settings</field> + <field name="inherit_id" ref="stock.res_config_settings_view_form"/> + <field name="arch" type="xml"> + <xpath expr="//div[@id='group_lot_on_delivery_slip']" position="after"> + <div class="col-12 col-lg-6 o_setting_box" attrs="{'invisible': ['|', ('group_lot_on_delivery_slip', '=', False), ('module_product_expiry', '=', False)]}" id="group_expiry_date_on_delivery_slip"> + <div class="o_setting_left_pane"> + <field name="group_expiry_date_on_delivery_slip"/> + </div> + <div class="o_setting_right_pane"> + <label for="group_expiry_date_on_delivery_slip"/> + <div class="text-muted"> + Expiration dates will appear on the delivery slip + </div> + </div> + </div> + </xpath> + </field> + </record> +</odoo> diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index 2dc95eef0b6..0d7a0bb1005 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -677,7 +677,8 @@ <group name="sale_shipping"> <label for="commitment_date" string="Delivery Date"/> <div name="commitment_date_div" class="o_row"> - <field name="commitment_date"/> <span class="text-muted">Expected: <field name="expected_date" widget="date"/></span> + <field name="commitment_date"/> + <span name="expected_date_span" class="text-muted">Expected: <field name="expected_date" widget="date"/></span> </div> </group> <group string="Reporting" name="sale_reporting" groups="base.group_no_one"> diff --git a/addons/sale_stock/views/sale_order_views.xml b/addons/sale_stock/views/sale_order_views.xml index 6a071b18457..88d5d56bb70 100644 --- a/addons/sale_stock/views/sale_order_views.xml +++ b/addons/sale_stock/views/sale_order_views.xml @@ -25,6 +25,11 @@ <field name="incoterm" widget="selection" groups="sale_stock.group_display_incoterm"/> <field name="picking_policy" required="True"/> </xpath> + <xpath expr="//span[@name='expected_date_span']" position="attributes"> + <attribute name="attrs"> + {'invisible': [('effective_date', '!=', False), ('commitment_date', '!=', False)]} + </attribute> + </xpath> <xpath expr="//div[@name='commitment_date_div']" position="replace"> <div class="o_row"> <field name="commitment_date"/> diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 079aad39305..6204341b12e 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -605,3 +605,32 @@ class StockMoveLine(models.Model): def _should_bypass_reservation(self, location): self.ensure_one() return location.should_bypass_reservation() or self.product_id.type != 'product' + + def _get_aggregated_product_quantities(self, **kwargs): + """ Returns a dictionary of products (key = id+name+description+uom) and corresponding values of interest. + + Allows aggregation of data across separate move lines for the same product. This is expected to be useful + in things such as delivery reports. Dict key is made as a combination of values we expect to want to group + the products by (i.e. so data is not lost). This function purposely ignores lots/SNs because these are + expected to already be properly grouped by line. + + returns: dictionary {product_id+name+description+uom: {product, name, description, qty_done, product_uom}, ...} + """ + aggregated_move_lines = {} + for move_line in self: + name = move_line.product_id.display_name + description = move_line.move_id.description_picking + if description == name or description == move_line.product_id.name: + description = False + uom = move_line.product_uom_id + line_key = str(move_line.product_id.id) + "_" + name + (description or "") + "uom " + str(uom.id) + + if line_key not in aggregated_move_lines: + aggregated_move_lines[line_key] = {'name': name, + 'description': description, + 'qty_done': move_line.qty_done, + 'product_uom': uom.name, + 'product': move_line.product_id} + else: + aggregated_move_lines[line_key]['qty_done'] += move_line.qty_done + return aggregated_move_lines diff --git a/addons/stock/report/report_deliveryslip.xml b/addons/stock/report/report_deliveryslip.xml index f6f56b10e86..24bd5e2366b 100644 --- a/addons/stock/report/report_deliveryslip.xml +++ b/addons/stock/report/report_deliveryslip.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> <odoo> - <template id="report_delivery_document"> <t t-call="web.html_container"> <t t-call="web.external_layout"> @@ -56,12 +55,13 @@ </tbody> </table> <table class="table table-sm mt48" t-if="o.move_line_ids and o.state=='done'" name="stock_move_line_table"> - <t t-set="has_serial_number" t-value="o.move_line_ids.mapped('lot_id')" /> + <t t-set="has_serial_number" t-value="False"/> + <t t-set="has_serial_number" t-value="o.move_line_ids.mapped('lot_id')" groups="stock.group_lot_on_delivery_slip"/> <thead> <tr> <th name="th_sml_product"><strong>Product</strong></th> - <t t-if="has_serial_number" groups="stock.group_lot_on_delivery_slip"> - <th name="lot_serial"> + <t name="lot_serial" t-if="has_serial_number"> + <th> Lot/Serial Number </th> </t> @@ -69,26 +69,59 @@ </tr> </thead> <tbody> - <tr t-foreach="o.move_line_ids" t-as="move_line"> - <td> - <span t-field="move_line.product_id"/> - <p t-if="move_line.move_id.description_picking != move_line.product_id.name"> - <span t-field="move_line.move_id.description_picking"/> - </p> - </td> - <t t-if="has_serial_number" groups="stock.group_lot_on_delivery_slip"> - <td t-if="move_line.lot_name"> - <span t-field="move_line.lot_name"/> - </td> - <td t-else=""> - <span t-field="move_line.lot_id.name"/> - </td> + <!-- This part gets complicated with different use cases (additional use cases in extensions of this report): + 1. If serial numbers are used and set to print on delivery slip => print lines as is, otherwise group them by overlapping + product + description + uom combinations + 2. If any packages are assigned => split products up by package (or non-package) and then apply use case 1 --> + <!-- If has destination packages => create sections of corresponding products --> + <t t-if="o.has_packages" name="has_packages"> + <t t-set="packages" t-value="o.move_line_ids.mapped('result_package_id')"/> + <t t-foreach="packages" t-as="package"> + <t t-call="stock.stock_report_delivery_package_section_line"/> + <t t-set="package_move_lines" t-value="o.move_line_ids.filtered(lambda l: l.result_package_id == package)"/> + <!-- If printing lots/serial numbers => keep products in original lines --> + <t t-if="has_serial_number"> + <tr t-foreach="package_move_lines" t-as="move_line"> + <t t-call="stock.stock_report_delivery_has_serial_move_line"/> + </tr> + </t> + <!-- If not printing lots/serial numbers => merge lines with same product+description+uom --> + <t t-else=""> + <t t-set="aggregated_lines" t-value="package_move_lines._get_aggregated_product_quantities()"/> + <t t-call="stock.stock_report_delivery_aggregated_move_lines"/> + </t> </t> - <td class="text-center"> - <span t-field="move_line.qty_done"/> - <span t-field="move_line.product_uom_id"/> - </td> - </tr> + <!-- Make sure we do another section for package-less products if they exist --> + <t t-set="move_lines" t-value="o.move_line_ids.filtered(lambda l: not l.result_package_id)"/> + <t t-if="move_lines" name="no_package_move_lines"> + <t t-call="stock.stock_report_delivery_no_package_section_line" name="no_package_section"/> + <t t-if="has_serial_number"> + <tr t-foreach="move_lines" t-as="move_line"> + <t t-call="stock.stock_report_delivery_has_serial_move_line"/> + </tr> + </t> + <t t-else=""> + <t t-set="aggregated_lines" t-value="move_lines._get_aggregated_product_quantities()"/> + <t t-if="aggregated_lines"> + <t t-call="stock.stock_report_delivery_aggregated_move_lines"/> + </t> + </t> + </t> + </t> + <!-- No destination packages --> + <t t-else=""> + <!-- If printing lots/serial numbers => keep products in original lines --> + <t t-if="has_serial_number"> + <tr t-foreach="o.move_line_ids" t-as="move_line"> + <t t-call="stock.stock_report_delivery_has_serial_move_line"/> + </tr> + </t> + <!-- If not printing lots/serial numbers => merge lines with same product --> + <t t-else="" name="aggregated_move_lines"> + <t t-set="aggregated_lines" t-value="o.move_line_ids._get_aggregated_product_quantities()"/> + <t t-call="stock.stock_report_delivery_aggregated_move_lines"/> + </t> + </t> </tbody> </table> <t t-set="backorders" t-value="o.backorder_ids.filtered(lambda x: x.state not in ('done', 'cancel'))"/> @@ -139,10 +172,63 @@ </t> </template> + <!-- templates for easier extension + cut back on repeat code due to multiple conditionals --> + <!-- move line(s) printing for tables --> + <template id="stock_report_delivery_has_serial_move_line"> + <td> + <span t-field="move_line.product_id"/> + <!-- this is an annoying workaround for the multiple types of descriptions (often auto-filled) that we do not want to print --> + <!-- this makes it so we can pre-filter the descriptions in inherited templates since we cannot extend the standard "if" condition --> + <!-- let's agree that pre-filtered descriptions will be set to "" --> + <t t-if="not description and description != ''"> + <t t-set="description" t-value="move_line.move_id.description_picking"/> + </t> + <p t-if="description !='' and description != move_line.product_id.name"> + <span t-esc="description"/> + </p> + </td> + <t t-if="has_serial_number" name="move_line_lot"> + <td><span t-field="move_line.lot_id.name"/></td> + </t> + <td class="text-center" name="move_line_lot_qty_done"> + <span t-field="move_line.qty_done"/> + <span t-field="move_line.product_uom_id"/> + </td> + </template> + <template id="stock_report_delivery_aggregated_move_lines"> + <tr t-foreach="aggregated_lines" t-as="line"> + <td> + <span t-esc="aggregated_lines[line]['name']"/> + <p t-if="aggregated_lines[line]['description']"> + <span t-esc="aggregated_lines[line]['description']"/> + </p> + </td> + <td class="text-center" name="move_line_aggregated_qty_done"> + <span t-esc="aggregated_lines[line]['qty_done']"/> + <span t-esc="aggregated_lines[line]['product_uom']"/> + </td> + </tr> + </template> + + <!-- package related "section lines" --> + <template id="stock_report_delivery_package_section_line"> + <tr t-att-class="'bg-200 font-weight-bold o_line_section'"> + <td colspan="99" name="package_info"> + <span t-field="package.name"/> + </td> + </tr> + </template> + <template id="stock_report_delivery_no_package_section_line"> + <tr t-att-class="'bg-200 font-weight-bold o_line_section'"> + <td colspan="99" name="no_package_info"> + <span>Products with no package assigned</span> + </td> + </tr> + </template> + <template id="report_deliveryslip"> <t t-foreach="docs" t-as="o"> <t t-call="stock.report_delivery_document" t-lang="o.partner_id.lang"/> </t> </template> - </odoo> |
