Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions product_kit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizard
16 changes: 16 additions & 0 deletions product_kit/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
'name': 'Product Kit',
'version': '1.0',
'depends': ['base', 'sale', 'product', 'stock'],
'application': True,
'installable': True,
'author': "odoo s.a",
'category': 'Tutorials',
'license': 'AGPL-3',
'data': [
'security/ir.model.access.csv',
'views/product_template_views.xml',
'views/sale_order_views.xml',
'views/kit_config_wizard_views.xml',
],
}
4 changes: 4 additions & 0 deletions product_kit/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import product_template
from . import product_kit_line
from . import sale_order
from . import sale_order_line
47 changes: 47 additions & 0 deletions product_kit/models/product_kit_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError


class ProductKitLine(models.Model):
_name = 'product.kit.line'
_description = 'Product Kit Line'
_rec_name = 'product_id'

product_tmpl_id = fields.Many2one(
'product.template',
string="Kit Product",
required=True,
ondelete='cascade',
)
product_id = fields.Many2one(
'product.product',
string="Component Product",
required=True,
)
quantity = fields.Float(
string="Quantity",
required=True,
default=1.0,
)

@api.constrains('product_id')
def _check_product_id(self):
"""Validate that the component product is not the same as the kit product itself."""
for line in self:
if line.product_id and line.product_id.product_tmpl_id == line.product_tmpl_id:
raise ValidationError(
_("A product cannot be a component of itself.")
)

def write(self, vals):
"""Prevent modification of restricted fields after creation."""
restricted_fields = {'product_id', 'quantity', 'product_tmpl_id'}
modified = restricted_fields & set(vals.keys())
if modified:
field_names = dict(self.fields_get(modified)).keys()
raise UserError(
_("Kit line fields (%s) cannot be modified after creation. "
"Delete and recreate the line instead.")
% ", ".join(field_names)
)
return super(ProductKitLine, self).write(vals)
16 changes: 16 additions & 0 deletions product_kit/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from odoo import fields, models


class ProductTemplate(models.Model):
_inherit = 'product.template'

is_kit = fields.Boolean(
string="Is a Kit",
help="Check if this product is a kit composed of multiple products.",
)
kit_product_ids = fields.One2many(
'product.kit.line',
'product_tmpl_id',
string="Kit Products",
help="Products that make up this kit.",
)
76 changes: 76 additions & 0 deletions product_kit/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from odoo import _, api, fields, models


class SaleOrder(models.Model):
_inherit = 'sale.order'

@api.depends('order_line.kit_line_ids', 'order_line.parent_kit_line_id')
def _compute_kit_lines_count(self):
for order in self:
order.kit_lines_count = len(
order.order_line.filtered(lambda l: l.kit_line_ids or l.parent_kit_line_id)
)

kit_lines_count = fields.Integer(
string="Kit Lines",
compute='_compute_kit_lines_count',
)

def action_open_kit_config_wizard(self):

self.ensure_one()

context = self.env.context.copy()
active_line_id = context.get('active_id')
return {
'type': 'ir.actions.act_window',
'res_model': 'kit.config.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'default_sale_order_id': self.id,
'default_sale_line_id': active_line_id,
'active_ids': self.ids,
},
}

def action_confirm(self):

res = super(SaleOrder, self).action_confirm()

for order in self:
lines_to_process = order.order_line.filtered(
lambda l: l.product_id and not l.kit_line_ids
and l.product_id.product_tmpl_id.is_kit
and l.product_id.product_tmpl_id.kit_product_ids
)
for line in lines_to_process:
kit_template = line.product_id.product_tmpl_id

section_seq = line.sequence + 1
self.env['sale.order.line'].create({
'order_id': order.id,
'display_type': 'line_section',
'name': _("Sub products of %s", line.name),
'sequence': section_seq,
})

for idx, kit_component in enumerate(kit_template.kit_product_ids):
vals = {
'order_id': order.id,
'product_id': kit_component.product_id.id,
'product_uom_qty': line.product_uom_qty * kit_component.quantity,
'price_unit': kit_component.product_id.lst_price,
'name': kit_component.product_id.display_name,
'parent_kit_line_id': line.id,
'sequence': section_seq + 1 + idx,
}
child_line = self.env['sale.order.line'].create(vals)
line.write({'kit_line_ids': [(4, child_line.id)]})

vals = {'product_uom_qty': 0}
if line.name and not line.name.startswith('[Kit] '):
vals['name'] = f"[Kit] {line.name}"
line.write(vals)

return res
84 changes: 84 additions & 0 deletions product_kit/models/sale_order_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from odoo import api, fields, models


class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'

kit_line_ids = fields.One2many(
'sale.order.line',
'parent_kit_line_id',
string="Kit Lines",
help="Sale order lines generated from exploding this kit.",
)
parent_kit_line_id = fields.Many2one(
'sale.order.line',
string="Parent Kit Line",
help="Parent kit sale order line that generated this line.",
ondelete='cascade',
)

is_kit = fields.Boolean(
string="Is Kit",
compute='_compute_is_kit',
help="Whether the product on this line is a kit.",
)

@api.depends('product_id')
def _compute_is_kit(self):
for line in self:
line.is_kit = bool(
line.product_id
and line.product_id.product_tmpl_id.is_kit
and line.product_id.product_tmpl_id.kit_product_ids
)

def action_open_kit_config_wizard(self):

self.ensure_one()
return {
'type': 'ir.actions.act_window',
'res_model': 'kit.config.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'default_sale_order_id': self.order_id.id,
'default_sale_line_id': self.id,
'active_ids': self.order_id.ids,
},
}

def unlink(self):

kit_child_lines = self.env['sale.order.line']
kit_section_lines = self.env['sale.order.line']
for line in self:
if line.kit_line_ids:
kit_child_lines |= line.kit_line_ids
# Find and remove the section header that sits between the kit line and its components
section_line = self.search([
('order_id', '=', line.order_id.id),
('display_type', '=', 'line_section'),
('sequence', '=', line.sequence + 1),
], limit=1)
if section_line:
kit_section_lines |= section_line
if kit_child_lines:
kit_child_lines.unlink()
if kit_section_lines:
kit_section_lines.unlink()
return super(SaleOrderLine, self).unlink()

def _get_kit_component_lines(self):

self.ensure_one()
return self.kit_line_ids

def _is_kit_line(self):

self.ensure_one()
return bool(self.kit_line_ids)

def _is_kit_component(self):

self.ensure_one()
return bool(self.parent_kit_line_id)
5 changes: 5 additions & 0 deletions product_kit/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink

access_product_kit_line,access.product.kit.line,model_product_kit_line,base.group_user,1,1,1,0
access_kit_config_wizard,access.kit.config.wizard,model_kit_config_wizard,base.group_user,1,1,1,1
access_kit_config_wizard_line,access.kit.config.wizard.line,model_kit_config_wizard_line,base.group_user,1,1,1,1
41 changes: 41 additions & 0 deletions product_kit/views/kit_config_wizard_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="kit_config_wizard_form_view" model="ir.ui.view">
<field name="name">kit.config.wizard.form</field>
<field name="model">kit.config.wizard</field>
<field name="arch" type="xml">
<form string="Configure Kit">
<group>
<field name="sale_order_id" invisible="1"/>
<field name="sale_line_id" invisible="1"/>
<field name="product_id" invisible="1"/>
</group>

<group>
<!-- <field name="product_image" widget="image" class="oe_avatar" options="{'size': [96, 96]}"/>-->
<field name="product_name"/>
<field name="order_name" string="Order"/>
<field name="kit_quantity" string="Quantity"/>

</group>

<group>
<field name="line_ids" nolabel="1">
<list editable="bottom" string="Components">
<field name="product_id" string="Product"/>
<field name="quantity" string="Quantity"/>
<field name="price_unit" string="Unit Price"/>
<field name="subtotal" string="Subtotal" sum="Total"/>
</list>
</field>
</group>

<footer>
<button name="action_confirm" type="object"
string="Confirm" class="btn-primary"/>
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
</odoo>
63 changes: 63 additions & 0 deletions product_kit/views/product_template_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="action_open_kit_lines" model="ir.actions.act_window">
<field name="name">Sub Products</field>
<field name="res_model">product.kit.line</field>
<field name="view_mode">list,form</field>
<field name="domain">[('product_tmpl_id', '=', active_id)]</field>
<field name="context">{'default_product_tmpl_id': active_id}</field>
</record>

<record id="product_kit_line_list_view" model="ir.ui.view">
<field name="name">product.kit.line.list</field>
<field name="model">product.kit.line</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="product_id"/>
<field name="quantity"/>
</list>
</field>
</record>


<record id="view_product_template_kit_form_inherit" model="ir.ui.view">
<field name="name">product.template.kit.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<div name="options" position="inside">
<span name="kit_option" class="d-inline-flex">
<field name="is_kit"/>
<label for="is_kit"/>
</span>
</div>

<div name="button_box" position="inside">
<button name="%(action_open_kit_lines)d" type="action"
class="btn oe_stat_button btn-outline-secondary flex-grow-1 flex-lg-grow-0"
invisible="not is_kit">
<i class="o_button_icon fa fa-fw fa-cubes me-1"/>
<span>Kit Components</span>
</button>
</div>

<xpath expr="//notebook/page[last()]" position="after">
<page string="Kit Components" name="kit_components"
invisible="not is_kit">
<field name="kit_product_ids"/>
</page>
</xpath>
</field>
</record>

<record id="view_product_template_kit_search_inherit" model="ir.ui.view">
<field name="name">product.template.kit.search.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_search_view"/>
<field name="arch" type="xml">
<filter name="filter_to_sell" position="after">
<filter string="Kit" name="is_kit" domain="[('is_kit', '=', True)]"/>
</filter>
</field>
</record>
</odoo>
30 changes: 30 additions & 0 deletions product_kit/views/sale_order_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>

<record id="view_sale_order_kit_form_inherit" model="ir.ui.view">
<field name="name">sale.order.kit.form.inherit</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">

<xpath expr="//field[@name='order_line']//list//field[@name='product_id']" position="before">
<button name="action_open_kit_config_wizard"
type="object"
icon="fa-cubes"
invisible="not is_kit" width="25px"
title="Configure Kit"/>
</xpath>

<xpath expr="//field[@name='order_line']//form//field[@name='product_id']" position="before">
<button name="action_open_kit_config_wizard"
type="object"
class="btn btn-link"
icon="fa-cubes"
invisible="not is_kit" width="25px"
title="Configure Kit"/>
</xpath>

</field>
</record>

</odoo>
2 changes: 2 additions & 0 deletions product_kit/wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import kit_config_wizard
from . import kit_config_wizard_line
Loading