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 sale_product_kit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizard
17 changes: 17 additions & 0 deletions sale_product_kit/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
'name': 'Product Kit',
'author': 'Ayush Khubchandani (aykhu)',
'license': 'LGPL-3',
'depends': ["sale", "product"],
'data': [
'security/ir.model.access.csv',
'views/product_views.xml',
'views/sale_order_views.xml',
'wizard/product_kit_wizard_views.xml',
],
'assets': {
'web.assets_backend': [
'sale_product_kit/static/src/js/product_kit_sale_order_line.js',
],
},
}
4 changes: 4 additions & 0 deletions sale_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 sale_order_line
from . import sale_order
from . import account_move
14 changes: 14 additions & 0 deletions sale_product_kit/models/account_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from odoo import models


class AccountMove(models.Model):
_inherit = 'account.move'

def _get_move_lines_to_report(self):
lines = super()._get_move_lines_to_report()
return lines.filtered(
lambda l: (
not l.sale_line_ids.is_kit_child
or l.sale_line_ids.order_id.print_kit_components
)
)
15 changes: 15 additions & 0 deletions sale_product_kit/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from odoo import api, fields, models
from odoo.exceptions import ValidationError


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

is_kit = fields.Boolean()
sub_product_ids = fields.Many2many('product.product')

@api.constrains("sub_product_ids")
def _check_no_self_product_reference(self):
for record in self:
if record.product_variant_id in record.sub_product_ids:
raise ValidationError("A product cannot be added as a sub-product in its own kit.")
16 changes: 16 additions & 0 deletions sale_product_kit/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from odoo import fields, models


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

print_kit_components = fields.Boolean()

def _get_order_lines_to_report(self):
lines = super()._get_order_lines_to_report()
return lines.filtered(
lambda l: (
not l.is_kit_child
or l.order_id.print_kit_components
)
)
17 changes: 17 additions & 0 deletions sale_product_kit/models/sale_order_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from odoo import fields, models


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

is_kit = fields.Boolean(related='product_template_id.is_kit')
parent_kit_line_id = fields.Many2one(
"sale.order.line",
ondelete="cascade",
)
child_kit_line_ids = fields.One2many(
"sale.order.line",
"parent_kit_line_id",
)
is_kit_child = fields.Boolean()
kit_component_price = fields.Float()
3 changes: 3 additions & 0 deletions sale_product_kit/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_create,perm_write,perm_unlink
access_product_kit_wizard,access_product_kit_wizard,model_product_kit_wizard,base.group_user,1,1,1,1
access_product_kit_wizard_line,access_product_kit_wizard_line,model_product_kit_wizard_line,base.group_user,1,1,1,1
47 changes: 47 additions & 0 deletions sale_product_kit/static/src/js/product_kit_sale_order_line.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { registry } from "@web/core/registry";
import {
SaleOrderLineListRenderer,
SaleOrderLineOne2Many,
} from "@sale/js/sale_order_line_field/sale_order_line_field";


export class ProductKitSaleOrderLineListRenderer extends SaleOrderLineListRenderer {

isKitChild(record) {
return !!record.data.is_kit_child;
}

isKitParent(record){
return !!record.data.is_kit;
}

isCellReadonly(column, record) {
return super.isCellReadonly(column, record) || (
this.isKitChild(record)
) || (
this.isKitParent(record)
);
}

displayDeleteIcon(record) {
return super.displayDeleteIcon(record)
&& !this.isKitChild(record);
}
}

export class ProductKitSaleOrderLineOne2Many extends SaleOrderLineOne2Many {
static components = {
...SaleOrderLineOne2Many.components,
ListRenderer: ProductKitSaleOrderLineListRenderer,
};
}

export const productKitSaleOrderLineOne2Many = {
...registry.category("fields").get("sol_o2m"),
component: ProductKitSaleOrderLineOne2Many,
};

registry.category("fields").add(
"product_kit_sol_o2m",
productKitSaleOrderLineOne2Many
);
17 changes: 17 additions & 0 deletions sale_product_kit/views/product_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="product_kit_form_view_inherit_product_template" model="ir.ui.view">
<field name="name">product.kit.form.inherit.product.template</field>
<field name="model">product.template</field>
<field name="priority">5</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='group_general']" position="inside">
<field name="is_kit"/>
<field name="sub_product_ids" widget='many2many_tags' invisible="not is_kit"/>
</xpath>
</field>
</record>

</odoo>
36 changes: 36 additions & 0 deletions sale_product_kit/views/sale_order_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_product_kit_wizard" model="ir.actions.act_window">
<field name="name">Product Kit Values</field>
<field name="res_model">product.kit.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>

<record id="sale_order_form_view_inherit_product_kit" model="ir.ui.view">
<field name="name">sale.order.form.product.kit</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']" position="attributes">
<attribute name="widget">product_kit_sol_o2m</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/list/field[@name='currency_id']" position="before">
<field name="is_kit_child" column_invisible="True"/>
</xpath>
<xpath expr="//field[@name='payment_term_id']" position="after">
<field name="print_kit_components"/>
</xpath>
<xpath expr="//field[@name='product_template_id']" position="after">
<button
name="%(action_product_kit_wizard)d"
type="action"
icon="fa-flask"
context="{'active_order_line_id': id}"
invisible="not is_kit or state!='draft'"
/>
</xpath>
</field>
</record>

</odoo>
1 change: 1 addition & 0 deletions sale_product_kit/wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import product_kit_wizard
84 changes: 84 additions & 0 deletions sale_product_kit/wizard/product_kit_wizard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from odoo import api, fields, models


class ProductKitWizard(models.TransientModel):
_name = "product.kit.wizard"
_description = "Product Kit Wizard"

@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
order_line = self.env['sale.order.line'].browse(
self.env.context.get('active_order_line_id')
)
if order_line.child_kit_line_ids:
wizard_lines = [
(0, 0, {
'sub_product_id': line.product_id.id,
'quantity': line.product_uom_qty,
'price': line.kit_component_price,
})
for line in order_line.child_kit_line_ids
]
else:
wizard_lines = [
(0, 0, {
'sub_product_id': sp.id,
'quantity': 1,
'price': sp.lst_price,
})
for sp in order_line.product_template_id.sub_product_ids
]
res.update({
'product_id': order_line.product_id.id,
'wizard_line_ids': wizard_lines,
})
return res

product_id = fields.Many2one('product.product')
wizard_line_ids = fields.One2many("product.kit.wizard.line", "wizard_id")

def add_sub_product_value(self):
parent_line = self.env['sale.order.line'].browse(
self.env.context.get('active_order_line_id')
)
total_price = 0
existing_children = {
line.product_id.id: line
for line in parent_line.child_kit_line_ids
}
create_vals = []
for wizard_line in self.wizard_line_ids:
total_price = total_price + (wizard_line.price * wizard_line.quantity)
product_id = wizard_line.sub_product_id.id
existing_child = existing_children.get(product_id)
if existing_child:
existing_child.write({
'product_uom_qty': wizard_line.quantity,
'price_unit': 0,
'kit_component_price': wizard_line.price,
})
else:
create_vals.append({
'order_id': parent_line.order_id.id,
'product_id': product_id,
'product_uom_qty': wizard_line.quantity,
'kit_component_price': wizard_line.price,
'price_unit': 0,
'parent_kit_line_id': parent_line.id,
'is_kit_child': True,
})
if create_vals:
self.env['sale.order.line'].create(create_vals)
parent_line.price_unit = total_price
return True


class ProductKitWizardLine(models.TransientModel):
_name = 'product.kit.wizard.line'
_description = 'Product Kit Wizard Lines'

wizard_id = fields.Many2one('product.kit.wizard')
sub_product_id = fields.Many2one('product.product')
quantity = fields.Integer(default=1)
price = fields.Float()
24 changes: 24 additions & 0 deletions sale_product_kit/wizard/product_kit_wizard_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id='product_kit_wizard_view_form' model='ir.ui.view'>
<field name='name'>product.kit.wizard.form</field>
<field name='model'>product.kit.wizard</field>
<field name="arch" type="xml">
<form>
<field name="wizard_line_ids">
<list editable="bottom" create="false" delete="false">
<field name="sub_product_id"/>
<field name="quantity"/>
<field name="price"/>
</list>
</field>
<footer>
<button string="Add" type="object" name="add_sub_product_value" class="btn-primary" data-hotkey="q"/>
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="x"/>
</footer>
</form>
</field>
</record>

</odoo>