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