[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>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
@@ -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'],
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user