Skip to content

Commit 16c82fa

Browse files
author
kuay-odoo
committed
[ADD] new_product_type: support for kit products in sale orders
Introduced support for kit products by adding an is kit flag and related sub-products on the product template. Sale order lines have been extended to differentiate between main and sub-products. Sub-products are now handled using related fields and custom logic for display pricing and subtotal computations. Wizard for adding sub-products to main lines introduced. Sub-product lines are made readonly in the UI and their appearance in reports is controlled by a boolean field print in report.
1 parent 5a2ff09 commit 16c82fa

14 files changed

+317
-0
lines changed

new_product_type/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import models
2+
from . import wizard

new_product_type/__manifest__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "New Product Type",
3+
"description": """
4+
"
5+
""",
6+
"version": "0.1",
7+
"depends": ['sale_management'],
8+
"data": [
9+
"security/ir.model.access.csv",
10+
"views/product_template.xml",
11+
"views/sale_order.xml",
12+
"views/sale_order_report.xml",
13+
"wizard/sale_order_wizard.xml"
14+
],
15+
"assets": {},
16+
"license": "AGPL-3",
17+
"installable": True,
18+
"auto-install": True,
19+
}

new_product_type/models/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from . import product_template
2+
from . import sale_order
3+
from . import sale_order_line
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from odoo import fields, models
2+
3+
4+
class ProductTemplate(models.Model):
5+
_inherit = "product.template"
6+
7+
is_kit = fields.Boolean(string="Is Kit?")
8+
sub_products = fields.Many2many("product.product", string="Sub Products")

new_product_type/models/sale_order.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from odoo import fields, models
2+
3+
4+
class SaleOrder(models.Model):
5+
_inherit = "sale.order"
6+
7+
print_in_report = fields.Boolean(string="Print in Report")
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from odoo import api, fields, models
2+
3+
4+
class SaleOrderLine(models.Model):
5+
_inherit = "sale.order.line"
6+
7+
is_kit = fields.Boolean(related="product_template_id.is_kit")
8+
is_sub_product_ol = fields.Boolean()
9+
main_order_line_id = fields.Many2one(
10+
"sale.order.line",
11+
string="Parent Line",
12+
ondelete="cascade",
13+
)
14+
15+
child_line_ids = fields.One2many(
16+
"sale.order.line",
17+
"main_order_line_id",
18+
string="Child Lines",
19+
)
20+
display_price = fields.Float(
21+
compute="_compute_display_price", inverse="_compute_unit_price"
22+
)
23+
display_sub_total = fields.Float(compute="_compute_amount_price")
24+
25+
@api.depends("price_unit", "is_sub_product_ol")
26+
def _compute_display_price(self):
27+
for line in self:
28+
line.display_price = 0.0 if line.is_sub_product_ol else line.price_unit
29+
30+
@api.depends("display_price", "price_subtotal", "is_sub_product_ol")
31+
def _compute_amount_price(self):
32+
for line in self:
33+
line.display_sub_total = (
34+
0.0 if line.is_sub_product_ol else line.price_subtotal
35+
)
36+
37+
def _compute_unit_price(self):
38+
for line in self:
39+
line.price_unit = line.display_price
40+
41+
def unlink(self):
42+
for line in self:
43+
line.main_order_line_id.price_subtotal -= (
44+
line.product_uom_qty * line.price_unit
45+
)
46+
47+
return super().unlink()
48+
49+
def open_sub_product_wizard(self):
50+
return {
51+
"name": "Sale order line wizard action",
52+
"type": "ir.actions.act_window",
53+
"res_model": "sale.order.wizard",
54+
"view_mode": "form",
55+
"target": "new",
56+
"context": {"active_id": self.id},
57+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2+
new_product_type.access_sale_order_wizard,access_sale_order_wizard,new_product_type.model_sale_order_wizard,base.group_user,1,1,1,1
3+
new_product_type.access_sale_order_line_wizard,access_sale_order_line_wizard,new_product_type.model_sale_order_line_wizard,base.group_user,1,1,1,1
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
<record id="product_template_form_view" model="ir.ui.view">
4+
<field name="name">product.template.form.view</field>
5+
<field name="model">product.template</field>
6+
<field name="inherit_id" ref="product.product_template_form_view" />
7+
<field name="arch" type="xml">
8+
<xpath expr="//field[@name='product_tooltip']" position="after">
9+
<group>
10+
<field name="is_kit" />
11+
<field name="sub_products" widget="many2many_tags" invisible="not is_kit" />
12+
</group>
13+
</xpath>
14+
</field>
15+
</record>
16+
</odoo>

new_product_type/views/sale_order.xml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0"?>
2+
<odoo>
3+
<record id="view_order_form" model="ir.ui.view">
4+
<field name="name">Sale.Order.View.Form.Inherit.kit</field>
5+
<field name="model">sale.order</field>
6+
<field name="inherit_id" ref="sale.view_order_form" />
7+
<field name="arch" type="xml">
8+
<xpath expr="//sheet//group[@name='sale_header']//group[@name='order_details']"
9+
position="inside">
10+
<field name="print_in_report" />
11+
</xpath>
12+
<xpath expr="//field[@name='product_template_id']" position="after">
13+
<button name='open_sub_product_wizard' type="object" string="Add Sub-Product"
14+
invisible="not is_kit or state=='sale'" />
15+
</xpath>
16+
<xpath expr="//field[@name='order_line']//list//field[@name='product_template_id']"
17+
position="attributes">
18+
<attribute name="readonly">is_sub_product_ol</attribute>
19+
</xpath>
20+
<xpath expr="//field[@name='order_line']//list//field[@name='product_uom_qty']"
21+
position="attributes">
22+
<attribute name="readonly">is_sub_product_ol</attribute>
23+
</xpath>
24+
<xpath expr="//field[@name='order_line']//list//field[@name='price_unit']"
25+
position="attributes">
26+
<attribute name="readonly">is_sub_product_ol</attribute>
27+
<attribute name="column_invisible">True</attribute>
28+
</xpath>
29+
<xpath expr="//field[@name='order_line']//list//field[@name='price_unit']"
30+
position="after">
31+
<field name="display_price" string="Unit Price" />
32+
</xpath>
33+
<xpath expr="//field[@name='order_line']//list//field[@name='display_price']"
34+
position="attributes">
35+
<attribute name="readonly">is_sub_product_ol</attribute>
36+
</xpath>
37+
<xpath expr="//field[@name='order_line']//list//field[@name='price_subtotal']"
38+
position="after">
39+
<field name="display_sub_total" string="Amount" />
40+
</xpath>
41+
<xpath expr="//field[@name='order_line']//list//field[@name='price_subtotal']"
42+
position="attributes">
43+
<attribute name="column_invisible">True</attribute>
44+
</xpath>
45+
</field>
46+
</record>
47+
</odoo>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0"?>
2+
<odoo>
3+
<template id="report_saleorder_document" inherit_id="sale.report_saleorder_document">
4+
<xpath expr="//t[@t-foreach='lines_to_report']/tr" position="attributes">
5+
<attribute name="t-if">(not line.is_sub_product_ol or (doc.print_in_report and
6+
line.is_sub_product_ol))</attribute>
7+
</xpath>
8+
</template>
9+
10+
<template id="sale_order_portal_content" inherit_id="sale.sale_order_portal_content">
11+
<xpath expr="//t[@t-foreach='lines_to_report']/tr" position="attributes">
12+
<attribute name="t-if">(not line.is_sub_product_ol or (doc.print_in_report and
13+
line.is_sub_product_ol))</attribute>
14+
</xpath>
15+
</template>
16+
</odoo>

new_product_type/wizard/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import sale_order_line_wizard
2+
from . import sale_order_line_wizard
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from odoo import fields, models
2+
3+
4+
class SaleOrderLines(models.TransientModel):
5+
_name = "sale.order.line.wizard"
6+
_description = "sale.order.lines.for.sub.products"
7+
8+
wizard_id = fields.Many2one("sale.order.wizard")
9+
product_id = fields.Many2one("product.product")
10+
quantity = fields.Float()
11+
wizard_price = fields.Float()
12+
price = fields.Float()
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from odoo import api, fields, models
2+
3+
4+
class SubProducts(models.TransientModel):
5+
_name = "sale.order.wizard"
6+
_description = "change.quantity.of.subproducts.in.kit"
7+
8+
main_product_id = fields.Many2one(
9+
"sale.order.line", string="Main Order Line", readonly=True
10+
)
11+
12+
line_ids = fields.One2many(
13+
"sale.order.line.wizard", "wizard_id", string="Sub Products"
14+
)
15+
16+
@api.model
17+
def default_get(self, fields_list):
18+
res = super().default_get(fields_list)
19+
active_id = self.env.context.get("active_id")
20+
main_order_line = self.env["sale.order.line"].browse(active_id)
21+
sale_order = main_order_line.order_id
22+
order_line = self.env["sale.order.line"].browse(active_id)
23+
res["main_product_id"] = main_order_line.id
24+
25+
if order_line:
26+
lines = []
27+
for product in order_line.product_template_id.sub_products:
28+
existing_line = self.env["sale.order.line"].search(
29+
[
30+
("order_id", "=", sale_order.id),
31+
("main_order_line_id", "=", main_order_line.id),
32+
("product_id", "=", product.id),
33+
],
34+
limit=1,
35+
)
36+
lines.append(
37+
(
38+
0,
39+
0,
40+
{
41+
"product_id": product.id,
42+
"quantity": existing_line.product_uom_qty
43+
if existing_line
44+
else 1,
45+
"price": existing_line.price_unit
46+
if existing_line
47+
else product.list_price,
48+
},
49+
)
50+
)
51+
res["line_ids"] = lines
52+
return res
53+
54+
def confirm_sub_products(self):
55+
active_id = self.env.context.get("active_id")
56+
main_order_line = self.env["sale.order.line"].browse(active_id)
57+
sale_order = main_order_line.order_id
58+
main_product_subtotal = (
59+
main_order_line.price_unit * main_order_line.product_uom_qty
60+
)
61+
for wizard in self:
62+
for line in wizard.line_ids:
63+
if line.quantity > 0:
64+
main_product_subtotal += line.quantity * line.price
65+
66+
existing_line = self.env["sale.order.line"].search(
67+
[
68+
("order_id", "=", sale_order.id),
69+
("main_order_line_id", "=", main_order_line.id),
70+
("product_id", "=", line.product_id.id),
71+
],
72+
limit=1,
73+
)
74+
75+
if existing_line:
76+
existing_line.write(
77+
{
78+
"product_uom_qty": line.quantity,
79+
"price_unit": line.price,
80+
}
81+
)
82+
else:
83+
self.env["sale.order.line"].create(
84+
{
85+
"order_id": sale_order.id,
86+
"product_id": line.product_id.id,
87+
"product_uom_qty": line.quantity,
88+
"name": line.product_id.name,
89+
"is_sub_product_ol": True,
90+
"price_unit": line.price,
91+
"main_order_line_id": main_order_line.id,
92+
}
93+
)
94+
main_order_line.write({"price_subtotal": main_product_subtotal})
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0"?>
2+
<odoo>
3+
<record id="sale_order_line_wizard_view_form" model="ir.ui.view">
4+
<field name="name">Sale order line wizard view form</field>
5+
<field name="model">sale.order.wizard</field>
6+
<field name="inherit_id" ref=""></field>
7+
<field name="arch" type="xml">
8+
<form string="Sub Products">
9+
<group>
10+
<div class="d-flex flex-column gap-2 ">
11+
<label for="main_product_id" string="Main Product" />
12+
<field name="main_product_id" class="fw-bold fs-2" nolabel="0" readonly="1" />
13+
<div class="fw-bold fs-4">Sub Products</div>
14+
<field name="line_ids" nolabel="1">
15+
<list editable="bottom" create="0">
16+
<field name="product_id" />
17+
<field name="quantity" />
18+
<field name="price" />
19+
</list>
20+
</field>
21+
</div>
22+
</group>
23+
<footer>
24+
<button class="btn btn-primary" type="object" string="Confirm"
25+
name="confirm_sub_products" />
26+
<button special="cancel" string="Cancel" />
27+
</footer>
28+
</form>
29+
</field>
30+
</record>
31+
</odoo>

0 commit comments

Comments
 (0)