first commit
This commit is contained in:
commit
57fae57177
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
17
README.md
Normal file
17
README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
POS Combo Tax
|
||||||
|
=============
|
||||||
|
|
||||||
|
Odoo 19 | Custom Addon
|
||||||
|
|
||||||
|
Author: Suherdy Yacob
|
||||||
|
|
||||||
|
This module applies sales tax on the combo product itself in POS instead of child lines.
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Shifts pricing and tax computations to the parent combo product order line.
|
||||||
|
- Sets child combo line unit prices and taxes to zero.
|
||||||
|
- Modifies backend invoicing logic to prevent invoicing parent combo line as a section line if it has a price.
|
||||||
|
- Correctly computes margin for the combo product parent line.
|
||||||
|
- Unhides the Customer Taxes field on the combo product form so taxes can be configured directly on the combo menu.
|
||||||
2
__init__.py
Normal file
2
__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import models
|
||||||
19
__manifest__.py
Normal file
19
__manifest__.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'POS Combo Tax',
|
||||||
|
'version': '1.0',
|
||||||
|
'category': 'Sales/Point of Sale',
|
||||||
|
'summary': 'Apply sales tax on combo products in POS',
|
||||||
|
'author': 'Suherdy Yacob',
|
||||||
|
'depends': ['point_of_sale', 'account'],
|
||||||
|
'data': [
|
||||||
|
'views/product_view.xml',
|
||||||
|
],
|
||||||
|
'assets': {
|
||||||
|
'point_of_sale._assets_pos': [
|
||||||
|
'pos_combo_tax/static/src/app/**/*',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'installable': True,
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
}
|
||||||
2
models/__init__.py
Normal file
2
models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import pos_order
|
||||||
45
models/pos_order.py
Normal file
45
models/pos_order.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import api, fields, models
|
||||||
|
from odoo.tools import float_is_zero
|
||||||
|
|
||||||
|
class PosOrder(models.Model):
|
||||||
|
_inherit = 'pos.order'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_invoice_lines_values(self, line_values, pos_line, move_type):
|
||||||
|
res = super()._get_invoice_lines_values(line_values, pos_line, move_type)
|
||||||
|
if pos_line.product_id.type == 'combo':
|
||||||
|
is_refund_order = bool(
|
||||||
|
pos_line.order_id.is_refund
|
||||||
|
or pos_line.order_id.amount_total < 0.0
|
||||||
|
)
|
||||||
|
qty_sign = -1 if (
|
||||||
|
(move_type == 'out_invoice' and is_refund_order)
|
||||||
|
or (move_type == 'out_refund' and not is_refund_order)
|
||||||
|
) else 1
|
||||||
|
res.update({
|
||||||
|
'display_type': False,
|
||||||
|
'product_id': line_values['product_id'].id,
|
||||||
|
'quantity': qty_sign * line_values['quantity'],
|
||||||
|
'discount': line_values['discount'],
|
||||||
|
'price_unit': line_values['price_unit'],
|
||||||
|
'name': line_values['name'],
|
||||||
|
'tax_ids': [(6, 0, line_values['tax_ids'].ids)],
|
||||||
|
'product_uom_id': line_values['uom_id'].id,
|
||||||
|
'extra_tax_data': self.env['account.tax']._export_base_line_extra_tax_data(line_values),
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
|
||||||
|
class PosOrderLine(models.Model):
|
||||||
|
_inherit = 'pos.order.line'
|
||||||
|
|
||||||
|
@api.depends('price_subtotal', 'total_cost')
|
||||||
|
def _compute_margin(self):
|
||||||
|
super()._compute_margin()
|
||||||
|
for line in self:
|
||||||
|
if line.product_id.type == 'combo':
|
||||||
|
sign = -1 if line.order_id.is_refund else 1
|
||||||
|
line.margin = (line.price_subtotal * sign) - line.total_cost
|
||||||
|
line.margin_percent = not float_is_zero(line.price_subtotal, precision_rounding=line.currency_id.rounding) \
|
||||||
|
and line.margin / (line.price_subtotal * sign) \
|
||||||
|
or 0
|
||||||
29
static/src/app/models/pos_order.js
Normal file
29
static/src/app/models/pos_order.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import { PosOrder } from "@point_of_sale/app/models/pos_order";
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
|
||||||
|
patch(PosOrder.prototype, {
|
||||||
|
setPricelist(pricelist) {
|
||||||
|
super.setPricelist(...arguments);
|
||||||
|
const combo_parent_lines = this.lines.filter(
|
||||||
|
(line) => line.price_type === "original" && line.combo_line_ids?.length
|
||||||
|
);
|
||||||
|
for (const line of combo_parent_lines) {
|
||||||
|
const newPrice = line.product_id.product_tmpl_id.getPrice(
|
||||||
|
pricelist,
|
||||||
|
line.getQuantity(),
|
||||||
|
line.getPriceExtra(),
|
||||||
|
false,
|
||||||
|
line.product_id
|
||||||
|
);
|
||||||
|
line.setUnitPrice(newPrice);
|
||||||
|
}
|
||||||
|
const combo_children_lines = this.lines.filter(
|
||||||
|
(line) => line.price_type === "original" && line.combo_parent_id
|
||||||
|
);
|
||||||
|
for (const line of combo_children_lines) {
|
||||||
|
line.setUnitPrice(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
22
static/src/app/models/pos_order_line.js
Normal file
22
static/src/app/models/pos_order_line.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import { PosOrderline } from "@point_of_sale/app/models/pos_order_line";
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
|
||||||
|
patch(PosOrderline.prototype, {
|
||||||
|
get displayPrice() {
|
||||||
|
if (this.combo_line_ids && this.combo_line_ids.length > 0) {
|
||||||
|
return this.config.iface_tax_included === "total" ? this.priceIncl : this.priceExcl;
|
||||||
|
}
|
||||||
|
return super.displayPrice;
|
||||||
|
},
|
||||||
|
|
||||||
|
get displayPriceNoDiscount() {
|
||||||
|
if (this.combo_line_ids && this.combo_line_ids.length > 0) {
|
||||||
|
return this.config.iface_tax_included === "total"
|
||||||
|
? this.priceInclNoDiscount
|
||||||
|
: this.priceExclNoDiscount;
|
||||||
|
}
|
||||||
|
return super.displayPriceNoDiscount;
|
||||||
|
}
|
||||||
|
});
|
||||||
33
static/src/app/services/pos_store.js
Normal file
33
static/src/app/services/pos_store.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/** @odoo-module */
|
||||||
|
|
||||||
|
import { PosStore } from "@point_of_sale/app/services/pos_store";
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
|
||||||
|
patch(PosStore.prototype, {
|
||||||
|
handlePriceUnit(values, order, price_unit) {
|
||||||
|
if (values.product_tmpl_id.isCombo() && price_unit === undefined) {
|
||||||
|
values.price_unit = values.product_id.getPrice(
|
||||||
|
order.pricelist_id,
|
||||||
|
values.qty,
|
||||||
|
values.price_extra,
|
||||||
|
false,
|
||||||
|
values.product_id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
super.handlePriceUnit(...arguments);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleComboProduct(values, order, configure = true, { line } = {}) {
|
||||||
|
const result = await super.handleComboProduct(...arguments);
|
||||||
|
if (result && values.combo_line_ids) {
|
||||||
|
for (const cmd of values.combo_line_ids) {
|
||||||
|
if (cmd[0] === "create") {
|
||||||
|
cmd[1].price_unit = 0;
|
||||||
|
cmd[1].tax_ids = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
});
|
||||||
16
views/product_view.xml
Normal file
16
views/product_view.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="product_template_form_view_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">product.template.form.inherit.pos.combo.tax</field>
|
||||||
|
<field name="model">product.template</field>
|
||||||
|
<field name="inherit_id" ref="account.product_template_form_view"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//label[@for='taxes_id']" position="attributes">
|
||||||
|
<attribute name="invisible">False</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//div[@name='taxes_div']" position="attributes">
|
||||||
|
<attribute name="invisible">False</attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue
Block a user