mirror of
https://github.com/OCA/stock-logistics-reporting.git
synced 2025-02-16 17:13:21 +02:00
[IMP] stock_picking_report_valued_sale_mrp: black, isort, prettier
This commit is contained in:
@@ -10,11 +10,6 @@
|
||||
"author": "Tecnativa, Odoo Community Association (OCA)",
|
||||
"maintainers": ["chienandalu"],
|
||||
"license": "AGPL-3",
|
||||
"depends": [
|
||||
"stock_picking_report_valued",
|
||||
"sale_mrp",
|
||||
],
|
||||
"data": [
|
||||
"report/stock_picking_report_valued.xml",
|
||||
],
|
||||
"depends": ["stock_picking_report_valued", "sale_mrp"],
|
||||
"data": ["report/stock_picking_report_valued.xml"],
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ from odoo import models
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = 'stock.move'
|
||||
_inherit = "stock.move"
|
||||
|
||||
def _get_components_per_kit(self):
|
||||
"""Compute how many kit components were demanded from this line. We
|
||||
@@ -16,13 +16,9 @@ class StockMove(models.Model):
|
||||
return 0
|
||||
component_demand = sum(
|
||||
sale_line.move_ids.filtered(
|
||||
lambda x: x.product_id == self.product_id and
|
||||
not x.origin_returned_move_id and
|
||||
(
|
||||
x.state != "cancel" or (
|
||||
x.state == "cancel" and x.backorder_id
|
||||
)
|
||||
)
|
||||
lambda x: x.product_id == self.product_id
|
||||
and not x.origin_returned_move_id
|
||||
and (x.state != "cancel" or (x.state == "cancel" and x.backorder_id))
|
||||
).mapped("product_uom_qty")
|
||||
)
|
||||
return component_demand / sale_line.product_uom_qty
|
||||
|
||||
@@ -4,7 +4,7 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class StockMoveLine(models.Model):
|
||||
_inherit = 'stock.move.line'
|
||||
_inherit = "stock.move.line"
|
||||
|
||||
phantom_product_id = fields.Many2one(
|
||||
comodel_name="product.product",
|
||||
@@ -14,20 +14,18 @@ class StockMoveLine(models.Model):
|
||||
readonly=True,
|
||||
)
|
||||
phantom_line = fields.Boolean(
|
||||
compute="_compute_sale_order_line_fields",
|
||||
compute_sudo=True,
|
||||
compute="_compute_sale_order_line_fields", compute_sudo=True,
|
||||
)
|
||||
phantom_delivered_qty = fields.Float(
|
||||
compute="_compute_sale_order_line_fields",
|
||||
compute_sudo=True,
|
||||
compute="_compute_sale_order_line_fields", compute_sudo=True,
|
||||
)
|
||||
|
||||
@api.depends("sale_line")
|
||||
def _compute_phantom_product_id(self):
|
||||
"""Relate every line with its kit product"""
|
||||
for line in self.filtered(
|
||||
lambda x: x.sale_line and
|
||||
x.sale_line.product_id._is_phantom_bom()):
|
||||
lambda x: x.sale_line and x.sale_line.product_id._is_phantom_bom()
|
||||
):
|
||||
line.phantom_product_id = line.sale_line.product_id
|
||||
|
||||
def _compute_sale_order_line_fields(self):
|
||||
@@ -37,8 +35,7 @@ class StockMoveLine(models.Model):
|
||||
super()._compute_sale_order_line_fields()
|
||||
kit_lines = self.filtered("phantom_product_id")
|
||||
for sale_line in kit_lines.mapped("sale_line"):
|
||||
move_lines = kit_lines.filtered(
|
||||
lambda x: x.sale_line == sale_line)
|
||||
move_lines = kit_lines.filtered(lambda x: x.sale_line == sale_line)
|
||||
# Deduct the kit quantity from the first component in the picking.
|
||||
# If the the kit is partially delivered, this could lead to an
|
||||
# unacurate value.
|
||||
@@ -47,42 +44,52 @@ class StockMoveLine(models.Model):
|
||||
continue
|
||||
price_unit = (
|
||||
sale_line.price_subtotal / sale_line.product_uom_qty
|
||||
if sale_line.product_uom_qty else sale_line.price_reduce)
|
||||
if sale_line.product_uom_qty
|
||||
else sale_line.price_reduce
|
||||
)
|
||||
# Compute how many kits were delivered from the components and
|
||||
# the original demand. Note that if the qty is edited in the sale
|
||||
# order this could lead to inconsitencies.
|
||||
components_per_kit = phantom_line.move_id._get_components_per_kit()
|
||||
phantom_line_qty_done = sum(move_lines.filtered(
|
||||
lambda x: x.product_id == phantom_line.product_id
|
||||
).mapped("qty_done"))
|
||||
phantom_line_qty_done = sum(
|
||||
move_lines.filtered(
|
||||
lambda x: x.product_id == phantom_line.product_id
|
||||
).mapped("qty_done")
|
||||
)
|
||||
quantity = phantom_line_qty_done / components_per_kit
|
||||
taxes = phantom_line.sale_tax_id.compute_all(
|
||||
price_unit=price_unit,
|
||||
currency=phantom_line.currency_id,
|
||||
quantity=quantity,
|
||||
product=phantom_line.product_id,
|
||||
partner=sale_line.order_id.partner_shipping_id)
|
||||
partner=sale_line.order_id.partner_shipping_id,
|
||||
)
|
||||
if sale_line.company_id.tax_calculation_rounding_method == (
|
||||
'round_globally'):
|
||||
price_tax = sum(
|
||||
t.get('amount', 0.0) for t in taxes.get('taxes', []))
|
||||
"round_globally"
|
||||
):
|
||||
price_tax = sum(t.get("amount", 0.0) for t in taxes.get("taxes", []))
|
||||
else:
|
||||
price_tax = taxes['total_included'] - taxes['total_excluded']
|
||||
phantom_line.update({
|
||||
'sale_tax_description': ', '.join(
|
||||
t.name or t.description for t in phantom_line.sale_tax_id),
|
||||
'sale_price_subtotal': taxes['total_excluded'],
|
||||
'sale_price_tax': price_tax,
|
||||
'sale_price_total': taxes['total_included'],
|
||||
'phantom_line': True,
|
||||
'phantom_delivered_qty': quantity,
|
||||
})
|
||||
price_tax = taxes["total_included"] - taxes["total_excluded"]
|
||||
phantom_line.update(
|
||||
{
|
||||
"sale_tax_description": ", ".join(
|
||||
t.name or t.description for t in phantom_line.sale_tax_id
|
||||
),
|
||||
"sale_price_subtotal": taxes["total_excluded"],
|
||||
"sale_price_tax": price_tax,
|
||||
"sale_price_total": taxes["total_included"],
|
||||
"phantom_line": True,
|
||||
"phantom_delivered_qty": quantity,
|
||||
}
|
||||
)
|
||||
# Remove the other lines
|
||||
redundant_lines = move_lines[1:]
|
||||
if redundant_lines:
|
||||
redundant_lines.update({
|
||||
'sale_tax_description': '',
|
||||
'sale_price_subtotal': 0,
|
||||
'sale_price_tax': 0,
|
||||
'sale_price_total': 0,
|
||||
})
|
||||
redundant_lines.update(
|
||||
{
|
||||
"sale_tax_description": "",
|
||||
"sale_price_subtotal": 0,
|
||||
"sale_price_tax": 0,
|
||||
"sale_price_total": 0,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,48 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
|
||||
<template id="valued_report_picking" inherit_id="stock_picking_report_valued.valued_report_picking">
|
||||
<xpath expr="//t[@t-if="move_line.picking_id.state != 'done'"]/.." position="attributes">
|
||||
<attribute name="t-if">o.valued and o.sale_id and o.move_line_ids and (move_line.phantom_line or not move_line.phantom_product_id)</attribute>
|
||||
<template
|
||||
id="valued_report_picking"
|
||||
inherit_id="stock_picking_report_valued.valued_report_picking"
|
||||
>
|
||||
<xpath
|
||||
expr="//t[@t-if="move_line.picking_id.state != 'done'"]/.."
|
||||
position="attributes"
|
||||
>
|
||||
<attribute
|
||||
name="t-if"
|
||||
>o.valued and o.sale_id and o.move_line_ids and (move_line.phantom_line or not move_line.phantom_product_id)</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//tr[@t-foreach='o.move_line_ids']" position="attributes">
|
||||
<attribute name="t-foreach">o.move_line_ids.filtered(lambda x: not x.phantom_product_id or x.phantom_line)</attribute>
|
||||
<attribute
|
||||
name="t-foreach"
|
||||
>o.move_line_ids.filtered(lambda x: not x.phantom_product_id or x.phantom_line)</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-field='move_line.product_id']" position="attributes">
|
||||
<attribute name="t-if">not move_line.phantom_line</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-field='move_line.product_id']" position="before">
|
||||
<span t-field="move_line.sale_line.product_id" t-if="move_line.phantom_line"/>
|
||||
<span
|
||||
t-field="move_line.sale_line.product_id"
|
||||
t-if="move_line.phantom_line"
|
||||
/>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-field='move_line.product_id.sudo().description_pickingout']" position="attributes">
|
||||
<xpath
|
||||
expr="//span[@t-field='move_line.product_id.sudo().description_pickingout']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="t-if">not move_line.phantom_line</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-field='move_line.product_id.sudo().description_pickingout']" position="before">
|
||||
<span t-field="move_line.sale_line.product_id.sudo().description_pickingout" t-if="move_line.phantom_line"/>
|
||||
<xpath
|
||||
expr="//span[@t-field='move_line.product_id.sudo().description_pickingout']"
|
||||
position="before"
|
||||
>
|
||||
<span
|
||||
t-field="move_line.sale_line.product_id.sudo().description_pickingout"
|
||||
t-if="move_line.phantom_line"
|
||||
/>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-field='move_line.qty_done']/.." position="attributes">
|
||||
<attribute name="t-if">not move_line.phantom_line</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-field='move_line.qty_done']/.." position="after">
|
||||
<td class="text-center" t-if="move_line.phantom_line">
|
||||
<span t-field="move_line.phantom_delivered_qty"/>
|
||||
<span t-field="move_line.sale_line.product_uom"/>
|
||||
</td>
|
||||
<span t-field="move_line.phantom_delivered_qty" />
|
||||
<span t-field="move_line.sale_line.product_uom" />
|
||||
</td>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-field='move_line.lot_id.name']" position="attributes">
|
||||
<attribute name="t-if">not move_line.phantom_line</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-field='move_line.lot_id.name']" position="after">
|
||||
<t t-set="kit_move_lines" t-value="move_line.sale_line.mapped('move_ids.move_line_ids').filtered(lambda x: x.sale_line == move_line.sale_line)" />
|
||||
<t
|
||||
t-set="kit_move_lines"
|
||||
t-value="move_line.sale_line.mapped('move_ids.move_line_ids').filtered(lambda x: x.sale_line == move_line.sale_line)"
|
||||
/>
|
||||
<t t-if="kit_move_lines and move_line.phantom_line">
|
||||
<table class="table-borderless">
|
||||
<t t-foreach="kit_move_lines.mapped('product_id')" t-as="kit_component">
|
||||
<t
|
||||
t-foreach="kit_move_lines.mapped('product_id')"
|
||||
t-as="kit_component"
|
||||
>
|
||||
<small class="font-italic" t-esc="kit_component.display_name" />
|
||||
<t t-if="kit_move_lines.mapped('lot_id')">
|
||||
<span class="font-italic">: </span>
|
||||
</t>
|
||||
<small t-esc="', '.join([k.lot_id.name for k in kit_move_lines.filtered(lambda x: x.lot_id and x.product_id == kit_component)])" />
|
||||
<br/>
|
||||
<small
|
||||
t-esc="', '.join([k.lot_id.name for k in kit_move_lines.filtered(lambda x: x.lot_id and x.product_id == kit_component)])"
|
||||
/>
|
||||
<br />
|
||||
</t>
|
||||
</table>
|
||||
</t>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# Copyright 2020 Tecnativa - David Vidal
|
||||
from odoo.addons.stock_picking_report_valued.tests\
|
||||
.test_stock_picking_valued import TestStockPickingValued
|
||||
from odoo.tests import Form
|
||||
|
||||
from odoo.addons.stock_picking_report_valued.tests.test_stock_picking_valued import (
|
||||
TestStockPickingValued,
|
||||
)
|
||||
|
||||
|
||||
class TestStockPickingValuedMrp(TestStockPickingValued):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""We want to run parent class tests again to ensure everything
|
||||
@@ -13,36 +14,37 @@ class TestStockPickingValuedMrp(TestStockPickingValued):
|
||||
super().setUpClass()
|
||||
cls.res_partner = cls.env["res.partner"]
|
||||
cls.product_product = cls.env["product.product"]
|
||||
cls.product_kit = cls.product_product.create({
|
||||
"name": "Product test 1",
|
||||
"type": "consu",
|
||||
})
|
||||
cls.product_kit_comp_1 = cls.product_product.create({
|
||||
"name": "Product Component 1",
|
||||
"type": "product",
|
||||
})
|
||||
cls.product_kit_comp_2 = cls.product_product.create({
|
||||
"name": "Product Component 2",
|
||||
"type": "product",
|
||||
})
|
||||
cls.bom = cls.env["mrp.bom"].create({
|
||||
"product_id": cls.product_kit.id,
|
||||
"product_tmpl_id": cls.product_kit.product_tmpl_id.id,
|
||||
"type": "phantom",
|
||||
"bom_line_ids": [
|
||||
(0, 0, {
|
||||
"product_id": cls.product_kit_comp_1.id,
|
||||
"product_qty": 2,
|
||||
}),
|
||||
(0, 0, {
|
||||
"product_id": cls.product_kit_comp_2.id,
|
||||
"product_qty": 4,
|
||||
})
|
||||
]})
|
||||
cls.product_2 = cls.product_product.create({
|
||||
"name": "Product test 2",
|
||||
"type": "product",
|
||||
})
|
||||
cls.product_kit = cls.product_product.create(
|
||||
{"name": "Product test 1", "type": "consu"}
|
||||
)
|
||||
cls.product_kit_comp_1 = cls.product_product.create(
|
||||
{"name": "Product Component 1", "type": "product"}
|
||||
)
|
||||
cls.product_kit_comp_2 = cls.product_product.create(
|
||||
{"name": "Product Component 2", "type": "product"}
|
||||
)
|
||||
cls.bom = cls.env["mrp.bom"].create(
|
||||
{
|
||||
"product_id": cls.product_kit.id,
|
||||
"product_tmpl_id": cls.product_kit.product_tmpl_id.id,
|
||||
"type": "phantom",
|
||||
"bom_line_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{"product_id": cls.product_kit_comp_1.id, "product_qty": 2},
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{"product_id": cls.product_kit_comp_2.id, "product_qty": 4},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
cls.product_2 = cls.product_product.create(
|
||||
{"name": "Product test 2", "type": "product"}
|
||||
)
|
||||
order_form = Form(cls.env["sale.order"])
|
||||
order_form.partner_id = cls.partner
|
||||
with order_form.order_line.new() as line_form:
|
||||
@@ -56,7 +58,8 @@ class TestStockPickingValuedMrp(TestStockPickingValued):
|
||||
# Maybe other modules create additional lines in the create
|
||||
# method in sale.order model, so let's find the correct line.
|
||||
cls.order_line = cls.sale_order_3.order_line.filtered(
|
||||
lambda r: r.product_id == cls.product_kit)
|
||||
lambda r: r.product_id == cls.product_kit
|
||||
)
|
||||
cls.order_out_picking = cls.sale_order_3.picking_ids
|
||||
|
||||
def test_01_picking_confirmed(self):
|
||||
|
||||
Reference in New Issue
Block a user