first commit
This commit is contained in:
commit
658bb6c72a
48
README.md
Normal file
48
README.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# POS Order Line Discount
|
||||||
|
|
||||||
|
This module converts order-level discounts (like global discount and loyalty program reward discount) to order line discounts in the Point of Sale.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Convert order-level discounts to line-level discounts
|
||||||
|
- Support for loyalty program rewards
|
||||||
|
- Configurable discount distribution methods
|
||||||
|
- Maintain proper tax calculations
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
1. Go to Point of Sale > Configuration > Point of Sale
|
||||||
|
2. Select your POS configuration
|
||||||
|
3. In the Pricing section, you'll find:
|
||||||
|
- **Line Discount Type**: Choose between percentage or fixed amount distribution
|
||||||
|
- **Apply Line Discount on Rewards**: Enable to convert loyalty rewards to line discounts
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
The module works by intercepting the reward application process and distributing the discount amount across all order lines instead of creating a separate reward line. This ensures that:
|
||||||
|
|
||||||
|
1. All discounts are visible at the line level
|
||||||
|
2. Tax calculations are properly maintained
|
||||||
|
3. Reporting shows discounts at the line level
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
The module patches the following components:
|
||||||
|
|
||||||
|
1. **PosOrder model**: Processes rewards as line discounts during order creation
|
||||||
|
2. **PosConfig model**: Adds configuration options for discount distribution
|
||||||
|
3. **Order model (JS)**: Overrides reward application to distribute discounts to lines
|
||||||
|
4. **Orderline model (JS)**: Enhances line discount handling
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
This module is compatible with Odoo 17 and requires:
|
||||||
|
- point_of_sale
|
||||||
|
- pos_loyalty
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Place the module in your Odoo addons directory
|
||||||
|
2. Update the apps list
|
||||||
|
3. Install the "POS Order Line Discount" module
|
||||||
|
4. Configure your POS settings as needed
|
||||||
3
__init__.py
Normal file
3
__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import models
|
||||||
23
__manifest__.py
Normal file
23
__manifest__.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
"name": "POS Order Line Discount",
|
||||||
|
"version": "1.0",
|
||||||
|
"category": "Point of Sale",
|
||||||
|
"summary": "Convert order-level discounts to line-level discounts in POS",
|
||||||
|
"description": """
|
||||||
|
This module converts order-level discounts (like global discount and loyalty program reward discount)
|
||||||
|
to order line discounts in the Point of Sale.
|
||||||
|
""",
|
||||||
|
"depends": ["point_of_sale", "pos_loyalty"],
|
||||||
|
"data": [
|
||||||
|
"views/pos_config_views.xml",
|
||||||
|
],
|
||||||
|
"assets": {
|
||||||
|
"point_of_sale._assets_pos": [
|
||||||
|
'pos_order_line_discount/static/src/**/*',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"installable": True,
|
||||||
|
"auto_install": False,
|
||||||
|
"license": "LGPL-3"
|
||||||
|
}
|
||||||
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
4
models/__init__.py
Normal file
4
models/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import pos_order
|
||||||
|
from . import pos_config
|
||||||
BIN
models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/pos_config.cpython-312.pyc
Normal file
BIN
models/__pycache__/pos_config.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/pos_order.cpython-312.pyc
Normal file
BIN
models/__pycache__/pos_order.cpython-312.pyc
Normal file
Binary file not shown.
18
models/pos_config.py
Normal file
18
models/pos_config.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class PosConfig(models.Model):
|
||||||
|
_inherit = 'pos.config'
|
||||||
|
|
||||||
|
line_discount_type = fields.Selection([
|
||||||
|
('percentage', 'Percentage'),
|
||||||
|
('fixed', 'Fixed Amount')
|
||||||
|
], string='Line Discount Type', default='percentage',
|
||||||
|
help="Determines how order-level discounts are distributed to order lines")
|
||||||
|
|
||||||
|
apply_line_discount_on_rewards = fields.Boolean(
|
||||||
|
string="Apply Line Discount on Rewards",
|
||||||
|
default=True,
|
||||||
|
help="If checked, loyalty rewards will be applied as line discounts instead of order-level discounts"
|
||||||
|
)
|
||||||
151
models/pos_order.py
Normal file
151
models/pos_order.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, api, fields
|
||||||
|
from odoo.tools import float_compare, float_round
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PosOrder(models.Model):
|
||||||
|
_inherit = 'pos.order'
|
||||||
|
|
||||||
|
def _distribute_discount_to_lines(self, order, discount_amount, is_percentage=False):
|
||||||
|
"""
|
||||||
|
Distribute an order-level discount amount to individual order lines
|
||||||
|
:param order: pos.order record
|
||||||
|
:param discount_amount: The discount amount to distribute
|
||||||
|
:param is_percentage: If True, discount_amount is a percentage; otherwise it's a fixed amount
|
||||||
|
:return: Dictionary with line_id as key and discount value as value
|
||||||
|
"""
|
||||||
|
if not order.lines:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Filter out reward lines and lines with zero price
|
||||||
|
regular_lines = order.lines.filtered(lambda l: not l.is_reward_line and l.price_subtotal > 0)
|
||||||
|
if not regular_lines:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Calculate total tax-exclusive amount for proportional distribution
|
||||||
|
total_amount = sum(line.price_subtotal for line in regular_lines)
|
||||||
|
if total_amount <= 0:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
line_discounts = {}
|
||||||
|
|
||||||
|
if is_percentage:
|
||||||
|
# Apply the same percentage discount to all regular lines
|
||||||
|
for line in regular_lines:
|
||||||
|
# For percentage discounts, we simply add the percentages (simplified approach)
|
||||||
|
# In a real scenario, you might want to compound them properly
|
||||||
|
new_discount = min(100, line.discount + discount_amount)
|
||||||
|
line_discounts[line.id] = new_discount
|
||||||
|
else:
|
||||||
|
# Distribute fixed amount proportionally based on line tax-exclusive subtotal
|
||||||
|
remaining_discount = discount_amount
|
||||||
|
lines_count = len(regular_lines)
|
||||||
|
|
||||||
|
for i, line in enumerate(regular_lines):
|
||||||
|
if i == lines_count - 1:
|
||||||
|
# Last line gets the remaining discount to avoid rounding issues
|
||||||
|
# Calculate the additional discount percentage for this line based on tax-exclusive price
|
||||||
|
if line.price_subtotal > 0:
|
||||||
|
additional_discount_percentage = (remaining_discount / line.price_subtotal) * 100
|
||||||
|
new_discount = min(100, line.discount + additional_discount_percentage)
|
||||||
|
else:
|
||||||
|
new_discount = line.discount
|
||||||
|
line_discounts[line.id] = new_discount
|
||||||
|
else:
|
||||||
|
# Calculate proportional discount for this line based on tax-exclusive price
|
||||||
|
line_ratio = line.price_subtotal / total_amount if total_amount > 0 else 0
|
||||||
|
line_discount_amount = discount_amount * line_ratio
|
||||||
|
# Calculate the additional discount percentage for this line based on tax-exclusive price
|
||||||
|
if line.price_subtotal > 0:
|
||||||
|
additional_discount_percentage = (line_discount_amount / line.price_subtotal) * 100
|
||||||
|
new_discount = min(100, line.discount + additional_discount_percentage)
|
||||||
|
else:
|
||||||
|
new_discount = line.discount
|
||||||
|
line_discounts[line.id] = new_discount
|
||||||
|
remaining_discount -= line_discount_amount
|
||||||
|
|
||||||
|
return line_discounts
|
||||||
|
|
||||||
|
def _apply_line_discounts(self, order, line_discounts):
|
||||||
|
"""
|
||||||
|
Apply calculated discounts to order lines
|
||||||
|
:param order: pos.order record
|
||||||
|
:param line_discounts: Dictionary with line_id as key and discount value as value
|
||||||
|
"""
|
||||||
|
for line in order.lines:
|
||||||
|
if line.id in line_discounts:
|
||||||
|
# Apply the calculated discount
|
||||||
|
line.discount = line_discounts[line.id]
|
||||||
|
# Trigger the onchange to recalculate the line amounts
|
||||||
|
line._onchange_amount_line_all()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _process_order_rewards_as_line_discounts(self, order):
|
||||||
|
"""
|
||||||
|
Process loyalty rewards as line discounts instead of order-level discounts
|
||||||
|
:param order: pos.order record
|
||||||
|
"""
|
||||||
|
if not order.config_id.apply_line_discount_on_rewards:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Find reward lines
|
||||||
|
reward_lines = order.lines.filtered(lambda l: l.is_reward_line)
|
||||||
|
|
||||||
|
for reward_line in reward_lines:
|
||||||
|
reward = reward_line.reward_id
|
||||||
|
if reward and reward.reward_type == 'discount':
|
||||||
|
# Calculate the discount amount that should be applied to regular lines
|
||||||
|
reward_amount = abs(reward_line.price_subtotal)
|
||||||
|
|
||||||
|
# Remove the reward line
|
||||||
|
reward_line.unlink()
|
||||||
|
|
||||||
|
# Distribute the discount to regular lines
|
||||||
|
is_percentage = (reward.discount_mode == 'percent')
|
||||||
|
discount_value = reward_amount if not is_percentage else reward.discount
|
||||||
|
|
||||||
|
line_discounts = self._distribute_discount_to_lines(
|
||||||
|
order,
|
||||||
|
discount_value,
|
||||||
|
is_percentage=is_percentage
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply the discounts to lines
|
||||||
|
self._apply_line_discounts(order, line_discounts)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _process_order(self, order, draft, existing_order):
|
||||||
|
"""
|
||||||
|
Override to process rewards as line discounts
|
||||||
|
"""
|
||||||
|
pos_order_id = super(PosOrder, self)._process_order(order, draft, existing_order)
|
||||||
|
|
||||||
|
# Convert order-level rewards to line discounts
|
||||||
|
if isinstance(pos_order_id, int):
|
||||||
|
pos_order = self.browse(pos_order_id)
|
||||||
|
self._process_order_rewards_as_line_discounts(pos_order)
|
||||||
|
|
||||||
|
return pos_order_id
|
||||||
|
|
||||||
|
|
||||||
|
class PosOrderLine(models.Model):
|
||||||
|
_inherit = 'pos.order.line'
|
||||||
|
|
||||||
|
def _compute_amount_line_all(self):
|
||||||
|
"""
|
||||||
|
Override to handle line-level discounts properly
|
||||||
|
"""
|
||||||
|
res = super(PosOrderLine, self)._compute_amount_line_all()
|
||||||
|
# Additional logic for line discounts can be added here if needed
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.onchange('discount')
|
||||||
|
def _onchange_discount(self):
|
||||||
|
"""
|
||||||
|
Override to handle line discount changes
|
||||||
|
"""
|
||||||
|
super(PosOrderLine, self)._onchange_discount()
|
||||||
|
# Additional logic for handling line discount changes can be added here
|
||||||
199
static/src/models.js
Normal file
199
static/src/models.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { Order, Orderline } from "@point_of_sale/app/store/models";
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
|
||||||
|
// Patch Order methods to handle line discounts
|
||||||
|
patch(Order.prototype, {
|
||||||
|
/**
|
||||||
|
* Override to calculate reward values as line discounts instead of order-level discounts
|
||||||
|
*/
|
||||||
|
_getRewardLineValues(args) {
|
||||||
|
const reward = args["reward"];
|
||||||
|
const coupon_id = args["coupon_id"];
|
||||||
|
|
||||||
|
// If config is not set to apply rewards as line discounts, use the original method
|
||||||
|
// Note: In JavaScript, we access the config through this.pos.config
|
||||||
|
if (!this.pos.config.apply_line_discount_on_rewards) {
|
||||||
|
return super._getRewardLineValues(...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For discount rewards, we'll distribute the discount to order lines instead of creating reward lines
|
||||||
|
if (reward.reward_type === "discount") {
|
||||||
|
// Calculate the reward using the original method first to get the discount amount
|
||||||
|
const originalRewardLines = super._getRewardLineValues(...arguments);
|
||||||
|
|
||||||
|
// Get the total discount amount from the reward lines (tax-excluded)
|
||||||
|
let totalDiscountAmount = 0;
|
||||||
|
originalRewardLines.forEach(line => {
|
||||||
|
// Calculate tax-excluded amount from the reward line
|
||||||
|
// The line.price is tax-included, so we need to calculate the tax-excluded amount
|
||||||
|
// Reward lines are plain objects, not Orderline instances, so we calculate manually
|
||||||
|
let taxExcludedAmount = Math.abs(line.price * line.quantity);
|
||||||
|
|
||||||
|
// If the line has tax information, we need to calculate the tax-excluded amount
|
||||||
|
if (line.tax_ids && line.tax_ids.length > 0) {
|
||||||
|
// Calculate total tax rate (simplified approach for multiple taxes)
|
||||||
|
let totalTaxRate = 0;
|
||||||
|
for (const taxId of line.tax_ids) {
|
||||||
|
const tax = this.pos.taxes_by_id[taxId];
|
||||||
|
if (tax && tax.amount_type === 'percent') {
|
||||||
|
totalTaxRate += tax.amount / 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalTaxRate > 0) {
|
||||||
|
taxExcludedAmount = Math.abs(line.price * line.quantity) / (1 + totalTaxRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalDiscountAmount += taxExcludedAmount;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Distribute the discount to order lines instead of creating reward lines
|
||||||
|
// Filter out reward lines by checking if they have a reward_id property
|
||||||
|
const orderLines = this.get_orderlines().filter(line => !line.is_reward_line);
|
||||||
|
const totalOrderAmount = orderLines.reduce((sum, line) => sum + line.get_price_without_tax(), 0);
|
||||||
|
|
||||||
|
if (totalOrderAmount > 0 && totalDiscountAmount > 0) {
|
||||||
|
// Apply the discount to each line proportionally based on its contribution to the total
|
||||||
|
orderLines.forEach(line => {
|
||||||
|
// Calculate the line's share of the total discount
|
||||||
|
const lineRatio = line.get_price_without_tax() / totalOrderAmount;
|
||||||
|
const lineDiscountAmount = totalDiscountAmount * lineRatio;
|
||||||
|
|
||||||
|
// Calculate the discount percentage for this line
|
||||||
|
// We want to set a discount that results in the lineDiscountAmount reduction
|
||||||
|
if (line.get_price_without_tax() > 0) {
|
||||||
|
const lineDiscountPercentage = (lineDiscountAmount / line.get_price_without_tax()) * 100;
|
||||||
|
// Set the discount percentage for this line (same as %Disc button)
|
||||||
|
// Ensure the discount percentage is within valid range [0, 100]
|
||||||
|
const validDiscountPercentage = Math.min(100, Math.max(0, lineDiscountPercentage));
|
||||||
|
// Mark this line as having a reward discount applied
|
||||||
|
if (line.originalDiscount === undefined) {
|
||||||
|
line.originalDiscount = line.discount;
|
||||||
|
}
|
||||||
|
// Apply the new discount on top of the original
|
||||||
|
line.discount = line.originalDiscount + validDiscountPercentage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return empty array since we're not creating reward lines
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-discount rewards, use the original method
|
||||||
|
return super._getRewardLineValues(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distribute an order-level discount to individual lines
|
||||||
|
*/
|
||||||
|
_distributeDiscountToLines(discountAmount, isPercentage = false) {
|
||||||
|
const lines = this.get_orderlines().filter(line => !line.reward_id);
|
||||||
|
if (lines.length === 0) return;
|
||||||
|
|
||||||
|
// Calculate total amount for proportional distribution
|
||||||
|
const totalAmount = lines.reduce((sum, line) => sum + line.get_price_without_tax(), 0);
|
||||||
|
if (totalAmount <= 0) return;
|
||||||
|
|
||||||
|
if (isPercentage) {
|
||||||
|
// Apply the same percentage discount to all lines
|
||||||
|
lines.forEach(line => {
|
||||||
|
const currentDiscount = line.get_discount();
|
||||||
|
// Combine discounts (this is a simplification)
|
||||||
|
// Store the original discount if not already stored
|
||||||
|
if (line.originalDiscount === undefined) {
|
||||||
|
line.originalDiscount = line.discount;
|
||||||
|
}
|
||||||
|
const newDiscount = Math.min(100, line.originalDiscount + discountAmount);
|
||||||
|
line.discount = newDiscount;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Distribute fixed amount proportionally based on line subtotal
|
||||||
|
lines.forEach(line => {
|
||||||
|
const lineRatio = line.get_price_without_tax() / totalAmount;
|
||||||
|
const lineDiscountAmount = discountAmount * lineRatio;
|
||||||
|
// Calculate the additional discount percentage for this line
|
||||||
|
if (line.get_price_without_tax() > 0) {
|
||||||
|
const additionalDiscountPercentage = (lineDiscountAmount / line.get_price_without_tax()) * 100;
|
||||||
|
// Store the original discount if not already stored
|
||||||
|
if (line.originalDiscount === undefined) {
|
||||||
|
line.originalDiscount = line.discount;
|
||||||
|
}
|
||||||
|
const newDiscountPercentage = Math.min(100, line.originalDiscount + additionalDiscountPercentage);
|
||||||
|
line.discount = newDiscountPercentage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a global discount to all order lines
|
||||||
|
*/
|
||||||
|
setGlobalDiscount(discountPercentage) {
|
||||||
|
this.get_orderlines().forEach(line => {
|
||||||
|
if (!line.is_reward_line) {
|
||||||
|
// Store the original discount if not already stored
|
||||||
|
if (line.originalDiscount === undefined) {
|
||||||
|
line.originalDiscount = line.discount;
|
||||||
|
}
|
||||||
|
line.discount = discountPercentage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to also reset line discounts applied by this module
|
||||||
|
*/
|
||||||
|
_resetPrograms() {
|
||||||
|
// First call the original reset method
|
||||||
|
super._resetPrograms(...arguments);
|
||||||
|
|
||||||
|
// Reset line discounts that were applied by this module
|
||||||
|
this.get_orderlines().forEach(line => {
|
||||||
|
if (!line.is_reward_line && line.originalDiscount !== undefined) {
|
||||||
|
// Reset to the original discount value
|
||||||
|
line.discount = line.originalDiscount;
|
||||||
|
delete line.originalDiscount;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all line discounts applied by this module
|
||||||
|
*/
|
||||||
|
clearLineDiscounts() {
|
||||||
|
this.get_orderlines().forEach(line => {
|
||||||
|
if (!line.is_reward_line && line.originalDiscount !== undefined) {
|
||||||
|
// Reset to the original discount value
|
||||||
|
line.discount = line.originalDiscount;
|
||||||
|
delete line.originalDiscount;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Patch Orderline methods to handle line discounts
|
||||||
|
patch(Orderline.prototype, {
|
||||||
|
/**
|
||||||
|
* Override to handle line discount changes
|
||||||
|
*/
|
||||||
|
set_discount(discount) {
|
||||||
|
// Call the original method
|
||||||
|
super.set_discount(...arguments);
|
||||||
|
|
||||||
|
// Store the original discount if not already stored and this is not a reward line
|
||||||
|
if (this.originalDiscount === undefined && !this.is_reward_line) {
|
||||||
|
this.originalDiscount = this.discount;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discount amount for this line
|
||||||
|
*/
|
||||||
|
get_discount_amount() {
|
||||||
|
return this.get_price_without_tax() * (this.get_discount() / 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
29
views/pos_config_views.xml
Normal file
29
views/pos_config_views.xml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="view_pos_config_form_line_discount" model="ir.ui.view">
|
||||||
|
<field name="name">pos.config.form.line.discount</field>
|
||||||
|
<field name="model">pos.config</field>
|
||||||
|
<field name="inherit_id" ref="point_of_sale.pos_config_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//sheet" position="inside">
|
||||||
|
<div class="row mt16">
|
||||||
|
<div class="col-12 col-lg-12" id="line_discount_options">
|
||||||
|
<h2>Line Discount Options</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 col-lg-6">
|
||||||
|
<label for="line_discount_type" string="Line Discount Type"/>
|
||||||
|
<field name="line_discount_type"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-lg-6">
|
||||||
|
<label for="apply_line_discount_on_rewards" string="Apply Line Discount on Rewards"/>
|
||||||
|
<field name="apply_line_discount_on_rewards"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue
Block a user