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