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
|