pos_order_line_discount/static/src/models.js
2025-09-02 15:25:52 +07:00

200 lines
8.8 KiB
JavaScript

/** @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);
}
});