From 1bb32ca8351d323cad4387758c4ba70ff809fc7f Mon Sep 17 00:00:00 2001 From: "zaid.matiyas@gmail.com" Date: Thu, 7 May 2026 17:00:20 +0530 Subject: [PATCH] fix: MRP and MSP in List View POS , Pin Code in the new customer and Rate Editable in the POS for non stock item --- .../components/sale/CreateCustomerDialog.vue | 23 +++++++- POS/src/components/sale/EditItemDialog.vue | 12 ++-- POS/src/components/sale/ItemsSelector.vue | 8 ++- POS/src/composables/useQuickAmounts.js | 55 +++++-------------- pos_next/api/customers.py | 3 + pos_next/api/invoices.py | 6 +- pos_next/api/items.py | 46 ++++++++++++---- 7 files changed, 90 insertions(+), 63 deletions(-) diff --git a/POS/src/components/sale/CreateCustomerDialog.vue b/POS/src/components/sale/CreateCustomerDialog.vue index fab7832e..9c04d229 100644 --- a/POS/src/components/sale/CreateCustomerDialog.vue +++ b/POS/src/components/sale/CreateCustomerDialog.vue @@ -98,7 +98,18 @@ - + +
+ + +
-

+

{{ __('Locked (offer applied)') }}

@@ -356,14 +356,16 @@ const hasPricingRules = computed(() => { }) // Rate editing is allowed only if: -// 1. POS Settings allows rate editing AND -// 2. Item does NOT have pricing rules (promotional offers) applied +// 1. POS Settings allows rate editing AND no pricing rules applied, OR +// 2. Item is not a stock item (is_stock_item == 0) const canEditRate = computed(() => { - return settingsStore.allowUserToEditRate && !hasPricingRules.value + return (settingsStore.allowUserToEditRate && !hasPricingRules.value) || !localItem.value?.is_stock_item }) // Tooltip message for why rate editing is disabled const rateEditDisabledReason = computed(() => { + if (canEditRate.value) return '' + if (!localItem.value?.is_stock_item) return '' // Should be editable for non-stock items if (!settingsStore.allowUserToEditRate) { return __('Rate editing is disabled') } @@ -691,7 +693,7 @@ function updateItem() { // ======================================================================== // RATE EDIT VALIDATION // ======================================================================== - if (settingsStore.allowUserToEditRate && isRateManuallyEdited) { + if ((settingsStore.allowUserToEditRate || !localItem.is_stock_item) && isRateManuallyEdited) { // Validate rate is positive if (localRate.value <= 0) { showError(__('Rate must be greater than zero')) diff --git a/POS/src/components/sale/ItemsSelector.vue b/POS/src/components/sale/ItemsSelector.vue index 0beefcaf..b0cfd6a0 100644 --- a/POS/src/components/sale/ItemsSelector.vue +++ b/POS/src/components/sale/ItemsSelector.vue @@ -508,7 +508,8 @@ {{ __('Image') }} {{ __('Name') }} {{ __('Code') }} - {{ __('Rate') }} + {{ __('MRP') }} + {{ __('MSP') }} {{ __('Qty') }} {{ __('UOM') }} @@ -556,8 +557,11 @@
{{ item.item_code }}
-
{{ formatCurrency(item.rate || item.price_list_rate || 0) }}
+
{{ formatCurrency(item.mrp || 0) }}
+ +
{{ formatCurrency(item.price_list_rate || item.rate || 0) }}
+
{ + if (value < 50) return 5 + if (value < 200) return 10 + if (value < 1000) return 20 + if (value < 10000) return 100 + if (value < 50000) return 500 + return 1000 } - // Minimum gap between suggestions (at least 5% or 5, whichever is larger) - const minGap = Math.max(5, exactAmount * 0.05) + const step = getSuggestionStep(remaining) + let nextAmount = Math.ceil((exactAmount + 1) / step) * step - // Helper to check if amount is far enough from existing amounts - const isFarEnough = (newAmt) => { - for (const existing of amounts) { - if (Math.abs(newAmt - existing) < minGap) return false - } - return true + while (amounts.size < 4) { + amounts.add(nextAmount) + nextAmount += step } - // Add round-up amounts for each denomination - for (const denom of denominations) { - if (amounts.size >= 4) break - - // Round up to next multiple of this denomination - const roundedUp = Math.ceil(remaining / denom) * denom - - // Add if it's meaningfully different from exact amount - if (roundedUp > exactAmount && isFarEnough(roundedUp)) { - amounts.add(roundedUp) - } - - // Also add one step higher for convenience (e.g., 350 when remaining is 299) - if (amounts.size < 4) { - const oneStepUp = roundedUp + denom - if (oneStepUp > exactAmount && isFarEnough(oneStepUp)) { - amounts.add(oneStepUp) - } - } - } - - // Convert to array, sort, and limit to 4 return Array.from(amounts) .filter((amt) => amt > 0) .sort((a, b) => a - b) diff --git a/pos_next/api/customers.py b/pos_next/api/customers.py index 70209123..af96d81d 100644 --- a/pos_next/api/customers.py +++ b/pos_next/api/customers.py @@ -78,6 +78,7 @@ def create_customer( customer_name, mobile_no=None, email_id=None, + custom_pincode=None, customer_group="Individual", territory="All Territories", company=None, @@ -90,6 +91,7 @@ def create_customer( customer_name (str): Customer name (required) mobile_no (str): Mobile number (optional) email_id (str): Email address (optional) + custom_pincode (str): Pincode (optional) customer_group (str): Customer group (default: Individual) territory (str): Territory (default: All Territories) company (str): Company (optional, used to auto-assign loyalty program) @@ -119,6 +121,7 @@ def create_customer( "territory": territory or "All Territories", "mobile_no": mobile_no or "", "email_id": email_id or "", + "custom_pincode": custom_pincode or "", "loyalty_program": loyalty_program, } ) diff --git a/pos_next/api/invoices.py b/pos_next/api/invoices.py index e56fd3fb..f99bb3a5 100644 --- a/pos_next/api/invoices.py +++ b/pos_next/api/invoices.py @@ -140,8 +140,10 @@ def validate_manual_rate_edit(item, pos_profile=None, pos_settings_cache=None): "message": _("POS Settings not found for profile {0}. Cannot validate rate edit.").format(pos_profile) } - # Check if rate editing is allowed - if not cint(pos_settings.get(FIELD_ALLOW_USER_TO_EDIT_RATE)): + is_stock_item = cint(item.get("is_stock_item") or 0) + + # Check if rate editing is allowed for stock items only + if is_stock_item and not cint(pos_settings.get(FIELD_ALLOW_USER_TO_EDIT_RATE)): return { "valid": False, "message": _("Rate editing is not allowed for this POS Profile") diff --git a/pos_next/api/items.py b/pos_next/api/items.py index 32aae89e..6503b4d3 100644 --- a/pos_next/api/items.py +++ b/pos_next/api/items.py @@ -1341,7 +1341,8 @@ def get_items(pos_profile, search_term=None, item_group=None, start=0, limit=20, .select( ItemPrice.item_code, ItemPrice.uom, - ItemPrice.price_list_rate + ItemPrice.price_list_rate, + ItemPrice.mrp_rate ) .where(ItemPrice.item_code.isin(item_codes)) .where(ItemPrice.price_list == pos_profile_doc.selling_price_list) @@ -1350,7 +1351,10 @@ def get_items(pos_profile, search_term=None, item_group=None, start=0, limit=20, .run(as_dict=True) ) for price in prices: - uom_prices_map.setdefault(price["item_code"], {})[price["uom"]] = price["price_list_rate"] + uom_prices_map.setdefault(price["item_code"], {})[price["uom"]] = { + "price_list_rate": price["price_list_rate"], + "mrp_rate": price.get("mrp_rate"), + } # Batch query stock for all items at once using Query Builder stock_map = {} @@ -1427,13 +1431,21 @@ def get_items(pos_profile, search_term=None, item_group=None, start=0, limit=20, # 1) Try price explicitly for stock UOM (preferred) if stock_uom and stock_uom in item_prices: - price_row = {"price_list_rate": item_prices[stock_uom], "uom": stock_uom} + price_row = { + "price_list_rate": item_prices[stock_uom].get("price_list_rate"), + "mrp_rate": item_prices[stock_uom].get("mrp_rate"), + "uom": stock_uom, + } # 2) If not found, try any price for the item (and capture its UOM) elif item_prices: # Get first available price first_uom = next(iter(item_prices.keys())) - price_row = {"price_list_rate": item_prices[first_uom], "uom": first_uom} + price_row = { + "price_list_rate": item_prices[first_uom].get("price_list_rate"), + "mrp_rate": item_prices[first_uom].get("mrp_rate"), + "uom": first_uom, + } # 3) If still not found and it's a template, derive min variant price derived_price = None @@ -1458,35 +1470,40 @@ def get_items(pos_profile, search_term=None, item_group=None, start=0, limit=20, # Finalize display price & display UOM display_rate = 0.0 display_uom = stock_uom + display_mrp = 0.0 if price_row: raw_rate = flt(price_row.get("price_list_rate") or 0) + mrp_value = flt(price_row.get("mrp_rate") or 0) price_uom = price_row.get("uom") or stock_uom if price_uom and stock_uom and price_uom != stock_uom: # convert to per-stock-UOM if possible cf = flt(conversion_map[item["item_code"]].get(price_uom) or 0) if cf: display_rate = raw_rate / cf + display_mrp = mrp_value / cf display_uom = stock_uom else: # no conversion available: show as is (price UOM) display_rate = raw_rate + display_mrp = mrp_value display_uom = price_uom else: display_rate = raw_rate + display_mrp = mrp_value display_uom = stock_uom elif derived_price is not None: display_rate = flt(derived_price) + display_mrp = 0.0 display_uom = stock_uom item["rate"] = display_rate item["price_list_rate"] = display_rate + item["mrp"] = display_mrp item["uom"] = display_uom item["price_uom"] = display_uom item["conversion_factor"] = 1 item["price_list_rate_price_uom"] = display_rate - - # =================================================================== # STOCK QUANTITY ASSIGNMENT: Stock Items vs Product Bundles # =================================================================== # Stock items: Use actual_qty from Bin table (direct stock tracking) @@ -1663,14 +1680,16 @@ def get_items_bulk(pos_profile, item_groups=None, start=0, limit=2000, include_v ItemPrice = DocType("Item Price") prices = ( frappe.qb.from_(ItemPrice) - .select(ItemPrice.item_code, ItemPrice.uom, ItemPrice.price_list_rate) + .select(ItemPrice.item_code, ItemPrice.uom, ItemPrice.price_list_rate, ItemPrice.mrp_rate) .where(ItemPrice.price_list == price_list) .where(ItemPrice.item_code.isin(item_codes)) - .where(ItemPrice.selling == 1) .run(as_dict=True) ) for p in prices: - uom_prices_map.setdefault(p.item_code, {})[p.uom] = flt(p.price_list_rate) + uom_prices_map.setdefault(p.item_code, {})[p.uom] = { + "price_list_rate": flt(p.price_list_rate), + "mrp_rate": flt(p.mrp_rate), + } # Stock warehouse = pos_profile_doc.warehouse @@ -1718,8 +1737,15 @@ def get_items_bulk(pos_profile, item_groups=None, start=0, limit=2000, include_v # Price: prefer stock_uom, then None/empty UOM (Item Price without UOM) prices = uom_prices_map.get(item_code, {}) - item["rate"] = flt(prices.get(stock_uom) or prices.get(None) or prices.get("") or 0) + price_row = ( + prices.get(stock_uom) + or prices.get(None) + or prices.get("") + or next(iter(prices.values()), {}) + ) + item["rate"] = flt(price_row.get("price_list_rate") or 0) item["price_list_rate"] = item["rate"] + item["mrp"] = flt(price_row.get("mrp_rate") or 0) item["uom"] = stock_uom item["price_uom"] = stock_uom item["conversion_factor"] = 1