Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7747732
feat(pricing): implement cheapest item discount functionality
MostafaKadry Jan 12, 2026
2fbb497
refactor(pricing): update discount application logic
MostafaKadry Jan 13, 2026
1321bf1
- Updated the discount application logic to ensure rates are calculat…
MostafaKadry Jan 13, 2026
d0e4df8
remove unwanted changes
MostafaKadry Jan 13, 2026
ac7eed1
remove unwanted changes
MostafaKadry Jan 13, 2026
c675c9b
fix(pricing): enhance pricing rule logic and error handling
MostafaKadry Jan 14, 2026
44658bd
perf(pricing-rule): optimize min/max price discount application
MostafaKadry Jan 22, 2026
fa37e63
feat(pricing): add support for Min/Max discount quantity limit
MostafaKadry Jan 25, 2026
dd51ec3
Merge branch 'develop' into apply_discount_to_min_price_items
MostafaKadry Jan 25, 2026
661a95e
FIX Critical Issues
MostafaKadry Jan 27, 2026
973e737
Merge branch 'develop' into apply_discount_to_min_price_items
MostafaKadry Jan 27, 2026
f75fc59
FIX Medium Issues and Minor Issues
MostafaKadry Jan 27, 2026
d206c5f
- Modified return behavior to skip standard pricing rule application …
MostafaKadry Jan 27, 2026
7e524a3
feat(cart): enhance offer validation and application process
MostafaKadry Jan 27, 2026
b2698e6
refactor(pricing): go with moky patching
MostafaKadry Jan 28, 2026
c49b877
refactor(pricing): streamline discount calculation and item update
MostafaKadry Jan 28, 2026
ed76513
Merge branch 'develop' into apply_discount_to_min_price_items and use…
MostafaKadry Mar 8, 2026
9f26b87
Refactor: Remove specific pricing rule fields from whitelisted list
MostafaKadry Mar 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions pos_next/api/invoices.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ def _validate_stock_on_invoice(invoice_doc):
items_to_check = [d.as_dict() for d in invoice_doc.items if d.get("is_stock_item")]

# Include packed items if present
if hasattr(invoice_doc, "packed_items"):
if getattr(invoice_doc, "packed_items", None):
items_to_check.extend([d.as_dict() for d in invoice_doc.packed_items])

# Check for stock errors
Expand Down Expand Up @@ -2681,7 +2681,7 @@ def apply_offers(invoice_data, selected_offers=None):
# Include both promotional scheme rules and standalone pricing rules
rule_map[record.name] = record

if selected_offer_names:
if selected_offers is not None:
# Restrict available rules to the ones explicitly selected from the UI.
rule_map = {
name: details
Expand Down Expand Up @@ -2816,6 +2816,30 @@ def apply_offers(invoice_data, selected_offers=None):
].promotional_scheme
free_items_map[(free_item.get("item_code"), rule_name)] = free_item_doc

# ========================================================================
# 2. APPLY MIN/MAX PRICING RULES (BULK EVALUATION)
# ========================================================================
# These rules require evaluating all items together to determine which
# items qualify based on price ranking. They were skipped by the standard
# engine above and now we evaluate them across the entire cart.
# ========================================================================
from pos_next.overrides.pricing_rule import apply_min_max_price_discounts

# Create a mock document for the bulk evaluator
# It needs 'items' (list of dicts) and price list info
mock_doc = frappe._dict({
"doctype": invoice.get("doctype") or "Sales Invoice",
"items": prepared_items,
"selling_price_list": pricing_args.price_list,
"company": pricing_args.company,
"customer": pricing_args.customer
})

# Determine allowed rules for Min/Max evaluator
allowed_rules = set(rule_map.keys()) if selected_offers is not None else None

apply_min_max_price_discounts(mock_doc, allowed_rules=allowed_rules)

return {
"items": [dict(item) for item in prepared_items],
"free_items": [dict(item) for item in free_items_map.values()],
Expand Down
114 changes: 0 additions & 114 deletions pos_next/fixtures/custom_field.json
Original file line number Diff line number Diff line change
Expand Up @@ -511,119 +511,5 @@
"translatable": 0,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": "0",
"depends_on": "eval:doc.selling",
"description": "If checked, this scheme will only apply to POS transactions (Sales Invoices with is_pos=1 and POS Invoices)",
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Promotional Scheme",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "pos_only",
"fieldtype": "Check",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "selling",
"is_system_generated": 0,
"is_virtual": 0,
"label": "POS Only",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2026-03-02 12:00:00.000000",
"module": "POS Next",
"name": "Promotional Scheme-pos_only",
"no_copy": 0,
"non_negative": 0,
"options": null,
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": "0",
"depends_on": null,
"description": "If checked, this rule only applies to POS transactions",
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Pricing Rule",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "pos_only",
"fieldtype": "Check",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "selling",
"is_system_generated": 0,
"is_virtual": 0,
"label": "POS Only",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2026-03-02 12:00:00.000000",
"module": "POS Next",
"name": "Pricing Rule-pos_only",
"no_copy": 0,
"non_negative": 0,
"options": null,
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": "eval:doc.promotional_scheme",
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
}
]
45 changes: 37 additions & 8 deletions pos_next/hooks.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
from pos_next.utils import get_build_version

app_name = "pos_next"
app_title = "POS Next"
app_publisher = "BrainWise"
app_description = "POS built on ERPNext that brings together real-time billing, stock management, multi-user access, offline mode, and direct ERP integration. Run your store or restaurant with confidence and control, while staying 100% open source."
app_email = "support@brainwise.me"
app_license = "agpl-3.0"

# Apps
# ------------------

Expand Down Expand Up @@ -101,8 +99,7 @@
"POS Profile-posa_allow_delete",
"POS Profile-posa_block_sale_beyond_available_qty",
"Mode of Payment-is_wallet_payment",
"Promotional Scheme-pos_only",
"Pricing Rule-pos_only"
"Promotional Scheme-pos_only"
]
]
]
Expand Down Expand Up @@ -190,7 +187,6 @@
# DocType Class
# ---------------
# Override standard doctype classes

override_doctype_class = {
"Sales Invoice": "pos_next.overrides.sales_invoice.CustomSalesInvoice"
}
Expand All @@ -214,7 +210,8 @@
"Sales Invoice": {
"validate": [
"pos_next.api.sales_invoice_hooks.validate",
"pos_next.api.wallet.validate_wallet_payment"
"pos_next.api.wallet.validate_wallet_payment",
"pos_next.overrides.pricing_rule.apply_min_max_price_discounts"
],
"before_cancel": "pos_next.api.sales_invoice_hooks.before_cancel",
"on_submit": [
Expand All @@ -227,6 +224,33 @@
"POS Profile": {
"on_update": "pos_next.realtime_events.emit_pos_profile_updated_event"
},
"Sales Order": {
"validate": "pos_next.overrides.pricing_rule.apply_min_max_price_discounts"
},
"Quotation": {
"validate": "pos_next.overrides.pricing_rule.apply_min_max_price_discounts"
},
"Delivery Note": {
"validate": "pos_next.overrides.pricing_rule.apply_min_max_price_discounts"
},
"POS Invoice": {
"validate": "pos_next.overrides.pricing_rule.apply_min_max_price_discounts"
},
"Purchase Order": {
"validate": "pos_next.overrides.pricing_rule.apply_min_max_price_discounts"
},
"Supplier Quotation": {
"validate": "pos_next.overrides.pricing_rule.apply_min_max_price_discounts"
},
"Purchase Receipt": {
"validate": "pos_next.overrides.pricing_rule.apply_min_max_price_discounts"
},
"Purchase Invoice": {
"validate": "pos_next.overrides.pricing_rule.apply_min_max_price_discounts"
},
"Opportunity": {
"validate": "pos_next.overrides.pricing_rule.apply_min_max_price_discounts"
},
"Promotional Scheme": {
"on_update": "pos_next.overrides.pricing_rule.sync_pos_only_to_pricing_rules"
}
Expand All @@ -247,15 +271,20 @@
"pos_next.tasks.branding_monitor.reset_tampering_counter",
],
}

# Testing
# -------

# before_tests = "pos_next.install.before_tests"

# Overriding Methods
# ------------------------------
#
try:
from erpnext.accounts.doctype.pricing_rule import pricing_rule as erpnext_pricing_rule
from pos_next.overrides.pricing_rule import apply_price_discount_rule as pos_next_apply_price_discount_rule
erpnext_pricing_rule.apply_price_discount_rule = pos_next_apply_price_discount_rule
except:
import frappe
frappe.log_error(frappe.get_traceback(), "Pricing Rule Override Error")
# override_whitelisted_methods = {
# "frappe.desk.doctype.event.event.get_events": "pos_next.event.get_events"
# }
Expand Down
Loading
Loading