Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
57 changes: 57 additions & 0 deletions posawesome/fixtures/custom_field.json
Original file line number Diff line number Diff line change
Expand Up @@ -6126,6 +6126,63 @@
"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": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "POS Profile",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_show_last_custom_rate",
"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": "custom_show_oem_part_number",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Show Last Custom Rate",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2025-07-10 22:07:23.406400",
"module": "POSAwesome",
"name": "POS Profile-custom_show_last_custom_rate",
"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
}


Expand Down
1 change: 1 addition & 0 deletions posawesome/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
"Item-custom_oem_part_number",
"POS Profile-custom_show_last_incoming_rate",
"POS Profile-custom_show_logical_rack",
"POS Profile-custom_show_last_custom_rate",
),
]
],
Expand Down
65 changes: 65 additions & 0 deletions posawesome/posawesome/api/posapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,14 +1302,39 @@ def _get_items_details(pos_profile, items_data, price_list=None):
items_data = json.loads(items_data)
show_rack = pos_profile.get("custom_show_logical_rack", 0)
show_last_incoming_rate = pos_profile.get("custom_show_last_incoming_rate", 0)
show_last_customer_rate = pos_profile.get("custom_show_last_custom_rate", 0)
warehouse = pos_profile.get("warehouse")
result = []
customer = pos_profile.get("customer")

if not price_list:
price_list = pos_profile.get("selling_price_list")

item_codes = [item.get("item_code") for item in items_data]

last_customer_rates = {}
if show_last_customer_rate and customer and item_codes:
customer_rates_data = frappe.db.sql("""
SELECT
sii.item_code,
sii.rate,
si.posting_date,
ROW_NUMBER() OVER (PARTITION BY sii.item_code ORDER BY si.posting_date DESC, si.creation DESC) as rn
FROM `tabSales Invoice Item` sii
INNER JOIN `tabSales Invoice` si ON si.name = sii.parent
WHERE si.customer = %(customer)s
AND sii.item_code IN %(item_codes)s
AND si.docstatus = 1
""", {
'customer': customer,
'item_codes': item_codes
}, as_dict=True)

for rate_data in customer_rates_data:
if rate_data.rn == 1:
last_customer_rates[rate_data.item_code] = rate_data.rate or 0


last_incoming_rates = {}
if show_last_incoming_rate and warehouse and item_codes:
bin_data = frappe.get_all(
Expand Down Expand Up @@ -1357,6 +1382,7 @@ def _get_items_details(pos_profile, items_data, price_list=None):
item_code = item.get("item_code")
custom_oem_part_number = frappe.db.get_value("Item", item_code, "custom_oem_part_number")
last_incoming_rate = last_incoming_rates.get(item_code, 0) if show_last_incoming_rate else 0
last_customer_rate = last_customer_rates.get(item_code, 0) if show_last_customer_rate else 0

rack_id = None
if show_rack:
Expand Down Expand Up @@ -1452,6 +1478,7 @@ def _get_items_details(pos_profile, items_data, price_list=None):
"custom_oem_part_number": custom_oem_part_number or "",
"last_incoming_rate": last_incoming_rate,
"rack_id": rack_id or "",
"last_customer_rate": last_customer_rate,
}
)

Expand Down Expand Up @@ -2740,3 +2767,41 @@ def get_app_info() -> Dict[str, List[Dict[str, str]]]:
})

return {"apps": apps_info}



@frappe.whitelist()
def get_last_customer_rate_value(customer, item_code):
"""
Get the last rate at which the customer purchased this item
Returns data in the same format as frappe.db.get_value
Mimics: frappe.db.get_value("Bin", {'item_code':item.item_code}, "valuation_rate")
"""
try:
if not customer or not item_code:
return {"last_customer_rate": 0}

# Get the most recent sales invoice rate for this customer and item
# Using the same logic as Bin -> valuation_rate but for Sales Invoice Item -> rate
result = frappe.db.sql("""
SELECT sii.rate
FROM `tabSales Invoice Item` sii
INNER JOIN `tabSales Invoice` si ON si.name = sii.parent
WHERE si.customer = %(customer)s
AND sii.item_code = %(item_code)s
AND si.docstatus = 1
ORDER BY si.posting_date DESC, si.creation DESC
LIMIT 1
""", {
'customer': customer,
'item_code': item_code
})

if result and len(result) > 0:
return {"last_customer_rate": result[0][0] or 0}

return {"last_customer_rate": 0}

except Exception as e:
frappe.log_error(f"Error getting last customer rate for {customer}, {item_code}: {str(e)}")
return {"last_customer_rate": 0}
4 changes: 3 additions & 1 deletion posawesome/public/js/posapp/components/pos/Invoice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,8 @@ export default {
{ title: __('QTY'), key: 'qty', align: 'center', required: true },
{ title: __('UOM'), key: 'uom', align: 'center', required: false },
{ title: __('Rate'), key: 'rate', align: 'center', required: true },
{ title: __('Last Inc Rate'), key: 'last_incoming_rate', align: 'center', required: false },
{ title: __('Inc.Rate'), key: 'last_incoming_rate', align: 'center', required: false },
{ title: __('LC Rate'), key: 'last_customer_rate', align: 'center', required: false },
{ title: __('Discount %'), key: 'discount_value', align: 'center', required: false },
{ title: __('Discount Amount'), key: 'discount_amount', align: 'center', required: false },
{ title: __('Amount'), key: 'amount', align: 'center', required: true },
Expand All @@ -477,6 +478,7 @@ export default {
if (col.key === 'discount_value' && this.pos_profile.posa_display_discount_percentage) return true;
if (col.key === 'discount_amount' && this.pos_profile.posa_display_discount_amount) return true;
if (col.key === 'last_incoming_rate' && this.pos_profile.custom_show_last_incoming_rate) return true;
if (col.key === 'last_customer_rate' && this.pos_profile.custom_show_last_custom_rate) return true;
if (col.key === 'rack_id' && this.pos_profile.custom_show_logical_rack) return true;
return false;
})
Expand Down
52 changes: 44 additions & 8 deletions posawesome/public/js/posapp/components/pos/ItemsSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export default {

watch: {
customer: _.debounce(function () {
this.update_items_details(this.items);
if (this.pos_profile.posa_force_reload_items) {
if (this.pos_profile.posa_smart_reload_mode) {
// When limit search is enabled there may be no items yet.
Expand Down Expand Up @@ -313,9 +314,11 @@ export default {
},
// Automatically search and add item whenever the query changes
first_search: _.debounce(function (val) {
// Call without arguments so search_onchange treats it like an Enter key
this.search_onchange();
}, 300),
// Only auto-search if checkbox is enabled
if (this.pos_profile && this.pos_profile.custom_auto_search_enter_items) {
this.search_onchange();
}
}, 300),

// Refresh item prices whenever the user changes currency
selected_currency() {
Expand Down Expand Up @@ -387,6 +390,17 @@ export default {
}
},

getLastCustomerRate(item_code) {
return frappe.call({
method: "posawesome.posawesome.api.posapp.get_last_customer_rate_value",
args: {
customer: this.customer,
item_code: item_code
}
});
},


async searchAndAddProductBundle(searchTerm) {
try {
console.log(`Searching for product bundle via API: ${searchTerm}`);
Expand Down Expand Up @@ -1254,17 +1268,15 @@ async searchAndAddProductBundle(searchTerm) {
key: "item_code",
},
{ title: __("Rate"), key: "rate", align: "start" },
{ title: __("Available QTY"), key: "actual_qty", align: "start" },
{ title: __("Avail.Qty"), key: "actual_qty", align: "start" },
{ title: __("UOM"), key: "stock_uom", align: "start" },
];
if (this.pos_profile && this.pos_profile.custom_show_oem_part_number) {
];
items_headers.push({
title: __("OEM Part No"),
align: "start",
sortable: true,
key: "custom_oem_part_number",
});
}
if (!this.pos_profile.posa_display_item_code) {
items_headers.splice(1, 1);
}
Expand Down Expand Up @@ -1337,6 +1349,21 @@ async searchAndAddProductBundle(searchTerm) {
}
item.qty = qtyVal;
}
// if (this.pos_profile.custom_show_last_custom_rate && this.customer) {
// this.getLastCustomerRate(this.customer, item.item_code)
// .then((r) => {
// if (r.message) {
// item.last_customer_rate = r.message.last_customer_rate
// }
// })
// .catch((error) => {
// console.warn(`Failed to fetch last customer rate:`, error);
// item.last_customer_rate = 8;
// });
// } else {
// item.last_customer_rate = 6;
// }

this.eventBus.emit("add_item", item);
this.qty = 1;
this.search = "";
Expand Down Expand Up @@ -1484,6 +1511,16 @@ async searchAndAddProductBundle(searchTerm) {
item.last_incoming_rate = r.message.valuation_rate
}
});
console.log(this.customer)
this.getLastCustomerRate(item.item_code)
.then((r) => {
if (r.message) {
// item.last_customer_rate = r.message.last_customer_rate
item.last_customer_rate =r.message.last_customer_rate
}
})


if (vm.pos_profile.custom_show_logical_rack) {
frappe.db.get_value("Logical Rack", {
'item': item.item_code,
Expand Down Expand Up @@ -2156,7 +2193,6 @@ async searchAndAddProductBundle(searchTerm) {
// Refresh item quantities when connection to server is restored
this.eventBus.on("server-online", async () => {
if (this.items && this.items.length > 0) {
await this.update_items_details(this.items);
}
});

Expand Down
Loading