Conversation
- Added new custom fields to Pricing Rule for applying discounts on the cheapest items. - Introduced a new function to patch the pricing rule application to support the new discount logic.
…ed correctly without compounding discounts.
- Introduced a new custom field for Pricing Rule to specify a quantity limit for applying discounts on items with minimum or maximum prices. - Updated the discount application logic to handle Min/Max pricing rules, ensuring discounts are applied correctly based on the specified quantity limits.
engahmed1190
left a comment
There was a problem hiding this comment.
Detailed Code Review: PR #95 - Apply Discount to Min/Max Price Items
Thank you for this feature! The concept of applying discounts to cheapest/most expensive items is valuable for promotions like "Buy 3, get the cheapest one 50% off". However, I've identified several issues that need to be addressed before merging.
🔴 Critical Issues
1. Missing Translation Import (Will Crash)
File: pos_next/overrides/pricing_rule.py, Line 126
frappe.throw(
_("Failed to apply pricing rule discounts..."), # _() is not imported!
title="Min/Max Pricing Rule Failed"
)Fix: Add from frappe import _ at the top of the file.
2. Monkey Patching at Module Load Time (Risky)
File: pos_next/hooks.py, Lines 255-260
try:
from erpnext.accounts.doctype.pricing_rule import pricing_rule as pricing_rule_module
from pos_next.overrides.pricing_rule import apply_price_discount_rule
pricing_rule_module.apply_price_discount_rule = apply_price_discount_rule
except Exception as e:
frappe.log_error(frappe.get_traceback(), "Error in pricing rule module")Problems:
- Runs at import time, before Frappe is fully initialized
frappe.log_error()may fail if called before DB connection is established- This affects ALL transactions globally, not just POS
Recommended Fix: Use after_migrate hook to apply the patch safely:
# In hooks.py
after_migrate = ["pos_next.install.after_migrate", "pos_next.overrides.pricing_rule.patch_pricing_rule"]
# In pricing_rule.py, add:
def patch_pricing_rule():
"""Safely patch the pricing rule module after Frappe is initialized."""
try:
from erpnext.accounts.doctype.pricing_rule import pricing_rule as pricing_rule_module
pricing_rule_module.apply_price_discount_rule = apply_price_discount_rule
except Exception:
frappe.log_error("Failed to patch pricing rule module for Min/Max discounts")3. qty_limit = 0 Bug (Critical Logic Error)
File: pos_next/overrides/pricing_rule.py, Lines 91, 100
remaining_qty = flt(pr.min_or_max_discount_qty_limit or 0) # Line 91
# ...
if remaining_qty <= 0: # Line 100 - Always true when limit is 0!
item.discount_percentage = 0.0
item.discount_amount = 0.0
continueProblem: The custom field description says "Leave 0 for no limit", but the code treats 0 as "no items get discount". This means if a user leaves the field empty or sets it to 0, NO DISCOUNT IS APPLIED AT ALL.
Fix:
# Use infinity for "no limit" when value is 0 or not set
limit = flt(pr.min_or_max_discount_qty_limit)
remaining_qty = limit if limit > 0 else float('inf')🟡 Medium Issues
4. Dead Code - Unreachable Branch
File: pos_next/overrides/pricing_rule.py, Lines 109-112
discount_qty = (
item.qty if remaining_qty <= 0 # ← This branch is NEVER reached!
else min(item.qty, remaining_qty)
)Why unreachable? Line 100 already handles remaining_qty <= 0 with continue, so this ternary's first condition can never be true.
Fix: Simplify to:
discount_qty = min(flt(item.qty), remaining_qty)5. Price List Check Uses Wrong Attribute
File: pos_next/overrides/pricing_rule.py, Line 74
if pr.for_price_list and pr.for_price_list != doc.selling_price_list:
continueProblem: Not all documents have selling_price_list. Delivery Notes and other documents may have different field names. This will fail silently.
Fix:
doc_price_list = (
doc.get("selling_price_list")
or doc.get("buying_price_list")
or doc.get("price_list")
)
if pr.for_price_list and pr.for_price_list != doc_price_list:
continue6. Missing pricing_rule_for Field
When apply_price_discount_rule() returns early for Min/Max rules, it doesn't set item_details.pricing_rule_for, which the original function sets. This could cause issues in downstream code.
Fix: Set it before returning:
if apply_discount_on_price in ["Min", "Max"]:
item_details.pricing_rule_for = pricing_rule.rate_or_discount
return7. Catch-Then-Throw Anti-Pattern
File: pos_next/overrides/pricing_rule.py, Lines 122-128
except Exception:
frappe.log_error(frappe.get_traceback(), "Min/Max Pricing Rule Failed")
frappe.throw(...) # Always throws, so why catch?Better approach: Let the exception propagate naturally, or only catch specific exceptions:
except frappe.ValidationError:
raise # Re-raise validation errors as-is
except Exception as e:
frappe.log_error(frappe.get_traceback(), "Min/Max Pricing Rule Failed")
frappe.throw(
_("Failed to apply pricing rule discounts: {0}").format(str(e)),
title=_("Pricing Rule Error")
)🟢 Minor Issues
8. Inconsistent Indentation
The file mixes tabs and 4-space indentation. Lines 19-45 use tabs, while lines 48-233 use 4 spaces. Please use consistent indentation (tabs preferred for this project).
9. Unnecessary frappe Import in hooks.py
File: pos_next/hooks.py, Line 2
import frappe # Only used in try/except blockThe frappe.log_error call happens at module load time when frappe may not be ready. This import should be removed if we fix the monkey-patching approach.
📊 Execution Flow Diagram
PHASE 1: Item Addition (get_item_details)
─────────────────────────────────────────
User adds item → get_pricing_rule_for_item() → apply_price_discount_rule()
│
┌───────────────────┴───────────────────┐
│ │
Standard Rule Min/Max Rule
(apply discount now) (SKIP - return early)
│ │
▼ ▼
item.discount_% = X item.pricing_rules = ["PRULE-001"]
(discount NOT applied yet)
PHASE 2: Document Validation (doc.validate)
───────────────────────────────────────────
User saves → doc_events["validate"] hook → apply_min_max_price_discounts()
│
▼
_collect_min_max_rule_items()
(find items with Min/Max rules)
│
▼
Sort items by price (asc for Min, desc for Max)
│
▼
Apply discount to first N items (by qty_limit)
│
▼
calculate_taxes_and_totals()
🧪 Test Cases Needed
- Basic Min discount: 3 items, discount on cheapest
- Basic Max discount: 3 items, discount on most expensive
- qty_limit = 0: Should apply to ALL items (currently broken)
- qty_limit = 2, item qty = 5: Partial quantity discount
- Multiple pricing rules: Different rules on same document
- Re-save document: Ensure discount isn't applied twice incorrectly
- Different document types: Sales Invoice, Sales Order, Quotation, POS Invoice
Summary
| Issue | Severity | Status |
|---|---|---|
Missing _() import |
🔴 Critical | Must fix |
| Monkey patch timing | 🔴 Critical | Must fix |
| qty_limit=0 bug | 🔴 Critical | Must fix |
| Dead code | 🟡 Medium | Should fix |
| Price list check | 🟡 Medium | Should fix |
| Missing pricing_rule_for | 🟡 Medium | Should fix |
| Catch-then-throw | 🟡 Medium | Should fix |
| Indentation | 🟢 Minor | Nice to fix |
Please address the critical issues before this PR can be merged. Happy to help with the fixes if needed!
…when Min/Max conditions are met.
- Consolidated offer re-validation and auto-application into a single operation to ensure accurate updates for Min/Max rules and cart-wide discounts. - Implemented client-side checks for invalid offers and new eligible offers before server communication. - Improved server response handling to update applied offers and provide user feedback on offer status. - Added error handling for offer synchronization to maintain cart integrity during operations.
- Simplified the discount calculation logic by directly updating item attributes in a single operation.
|
@engahmed1190 @goondeal |
PR #95 Deep Review — All Issues with FixesI've done a thorough analysis of this PR against ERPNext's Pricing Rule / Promotional Scheme internals. The core architectural idea (deferring Min/Max evaluation to a post-processing step) is sound, but there are several issues that need addressing before merge. Issue 1 (Critical): Bare
|
| # | Severity | File | Issue |
|---|---|---|---|
| 1 | Critical | hooks.py | Bare except: — silent failure = double discounts |
| 2 | Critical | pricing_rule.py | calculate_taxes_and_totals() overwrites blended rates |
| 3 | Critical | pricing_rule.py | discount_amount stored as total, ERPNext expects per-unit |
| 4 | Critical | pricing_rule.py | Sets pricing_rule (singular) — field doesn't exist |
| 5 | High | pricing_rule.py | Reset wipes discounts from other pricing rules |
| 6 | High | pricing_rule.py + fixture | qty_limit=0 = "only 1 item" but label says "no limit" |
| 7 | High | hooks.py | Purchase doc hooks unnecessary for POS app |
| 8 | Medium | pricing_rule.py | Override skips margin handling from original function |
| 9 | Medium | invoices.py | Variable name mismatch in selected_offers condition |
| 10 | Medium | invoices.py | Min/Max rules missing from applied_rules response |
| 11 | Medium | pricing_rule.py | Rate type can produce negative discount |
| 12 | Low | posCart.js | Consolidated flow loses per-step error isolation |
| 13 | Low | pricing_rule.py | Mixed tabs and spaces |
| 14 | Low | pricing_rule.py | Exception swallowed — invoice saves with wrong prices |
… export customizations instead of fixtures for pos_only check box field in pricing rule
|
This pull request has been automatically marked as stale because it has not had recent activity for 21 days. To keep this PR open, please:
If no further activity occurs within the next 14 days, this PR will be automatically closed. |
No description provided.