+
diff --git a/AntPos/src/components/Sidebar.vue b/AntPos/src/components/Sidebar.vue
index ed89a2e..22e2a51 100644
--- a/AntPos/src/components/Sidebar.vue
+++ b/AntPos/src/components/Sidebar.vue
@@ -103,7 +103,7 @@ import { inject, h, computed } from 'vue';
import { getSettings } from '@/stores/settings'
import { usersStore } from '@/stores/users';
import { useSidebar } from '@/stores/sidebar';
-import { usePermissionStore } from '@/stores/permissionStore';
+import { usePermissionStore } from '@/stores/permission';
import { useSessionStore } from '@/stores/session';
const sidebarStore = useSidebar()
diff --git a/AntPos/src/main.js b/AntPos/src/main.js
index 02f61e8..46f1a75 100644
--- a/AntPos/src/main.js
+++ b/AntPos/src/main.js
@@ -35,13 +35,6 @@ app.component('Input', Input)
app.use(pinia)
-const base = reactive({
- customer: {},
- items:[],
- invoice:{},
-})
-app.provide('base', base)
-
app.provide('dynamicComponent', useDynamicComponent());
// Provide emitter it globally
diff --git a/AntPos/src/pages/Payments.vue b/AntPos/src/pages/Payments.vue
index 1214fd2..c2374a9 100644
--- a/AntPos/src/pages/Payments.vue
+++ b/AntPos/src/pages/Payments.vue
@@ -5,12 +5,15 @@
\ No newline at end of file
diff --git a/AntPos/src/pages/Pos.vue b/AntPos/src/pages/Pos.vue
index 6e1686f..a66dcbf 100644
--- a/AntPos/src/pages/Pos.vue
+++ b/AntPos/src/pages/Pos.vue
@@ -6,11 +6,13 @@
diff --git a/AntPos/src/stores/payment.js b/AntPos/src/stores/payment.js
new file mode 100644
index 0000000..5ae6c3a
--- /dev/null
+++ b/AntPos/src/stores/payment.js
@@ -0,0 +1,36 @@
+import { defineStore } from 'pinia';
+import { ref } from 'vue';
+import { generateTempName, createDoctypeResource } from '@/utils';
+
+export const usePaymentStore = defineStore('PaymentEntry', () => {
+ const payment = ref({});
+ const paymentCustomer = ref({});
+
+ const paymentResource = createDoctypeResource('Payment Entry', (data) => {
+ payment.value = {
+ ...data,
+ name: generateTempName(data.doctype),
+ };
+ });
+
+ function unmount() {
+ payment.value = {};
+ paymentCustomer.value = {};
+ }
+
+ async function unmountAndRefresh(includeCustomer) {
+ payment.value = {};
+ if (!includeCustomer) {
+ paymentCustomer.value = {};
+ }
+ await paymentResource.fetch();
+ }
+
+ return {
+ payment,
+ paymentResource,
+ paymentCustomer,
+ unmount,
+ unmountAndRefresh,
+ };
+});
diff --git a/AntPos/src/stores/permissionStore.js b/AntPos/src/stores/permission.js
similarity index 100%
rename from AntPos/src/stores/permissionStore.js
rename to AntPos/src/stores/permission.js
diff --git a/AntPos/src/stores/pos.js b/AntPos/src/stores/pos.js
new file mode 100644
index 0000000..1a14e78
--- /dev/null
+++ b/AntPos/src/stores/pos.js
@@ -0,0 +1,41 @@
+import { defineStore } from 'pinia';
+import { ref } from 'vue';
+import { generateTempName, createDoctypeResource } from '@/utils';
+
+export const useInvoiceStore = defineStore('salesInvoice', () => {
+ const invoice = ref({});
+ const items = ref([]);
+ const invoiceCustomer = ref({});
+
+ const invoiceResource = createDoctypeResource('Sales Invoice', (data) => {
+ invoice.value = {
+ ...data,
+ name: generateTempName(data.doctype),
+ };
+ });
+
+ function unmount() {
+ invoice.value = {};
+ items.value = [];
+ invoiceCustomer.value = {};
+ }
+
+ async function unmountAndRefresh(includeCustomer) {
+ invoice.value = {};
+ items.value = [];
+ await invoiceResource.fetch();
+
+ if (!includeCustomer) {
+ invoiceCustomer.value = {};
+ }
+ }
+
+ return {
+ invoice,
+ items,
+ invoiceCustomer,
+ invoiceResource,
+ unmount,
+ unmountAndRefresh,
+ };
+});
diff --git a/AntPos/src/stores/session.js b/AntPos/src/stores/session.js
index b557756..f7ba856 100644
--- a/AntPos/src/stores/session.js
+++ b/AntPos/src/stores/session.js
@@ -4,7 +4,7 @@ import { userResource } from '@/stores/user'
import router from '@/router'
import { ref, computed } from 'vue'
import { usePosProfileStore } from '@/stores/posProfile'
-import { usePermissionStore } from '@/stores/permissionStore';
+import { usePermissionStore } from '@/stores/permission';
export const useSessionStore = defineStore('antpos-session', () => {
diff --git a/AntPos/src/utils/index.js b/AntPos/src/utils/index.js
index 8633ff1..35ef0fd 100644
--- a/AntPos/src/utils/index.js
+++ b/AntPos/src/utils/index.js
@@ -1,4 +1,4 @@
-import { toast } from 'frappe-ui'
+import { toast, createResource } from 'frappe-ui'
export function createToast(options) {
toast.create({
@@ -32,3 +32,28 @@ function htmlToText(html) {
div.innerHTML = html;
return div.textContent || div.innerText || "";
}
+
+export function generateTempName(doctype) {
+ const suffix = Math.random().toString(36).substring(2, 10)
+ return `new-${doctype.toLowerCase().replace(/\s/g, "-")}-${suffix}`
+};
+
+// Factory function (can also be extracted to a separate file)
+export function createDoctypeResource(doctype, onSuccessCallback) {
+ return createResource({
+ url: 'ant_pos.ant_pos.api.get_doc_field',
+ method: 'POST',
+ auto: false,
+ makeParams(params) {
+ return {
+ doctype,
+ ...params
+ }
+ },
+ onSuccess(data) {
+ if (data) {
+ onSuccessCallback(data)
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/ant_pos/ant_pos/api/__init__.py b/ant_pos/ant_pos/api/__init__.py
index 58fa35b..d89531f 100644
--- a/ant_pos/ant_pos/api/__init__.py
+++ b/ant_pos/ant_pos/api/__init__.py
@@ -102,4 +102,18 @@ def get_translations():
else:
language = frappe.db.get_single_value("System Settings", "language")
- return get_all_translations(language)
\ No newline at end of file
+ return get_all_translations(language)
+
+@frappe.whitelist()
+def get_doc_field():
+ doctype = frappe.form_dict.get('doctype')
+
+ if not doctype:
+ frappe.throw("Missing 'doctype' parameter")
+
+ try:
+ doc = frappe.new_doc(doctype)
+ return doc.as_dict()
+ except Exception as e:
+ frappe.log_error(frappe.get_traceback(), "get_doc_field error")
+ frappe.throw(f"Unable to create doc for {doctype}: {str(e)}")
\ No newline at end of file
diff --git a/ant_pos/ant_pos/api/item.py b/ant_pos/ant_pos/api/item.py
index 7900fad..970aa64 100644
--- a/ant_pos/ant_pos/api/item.py
+++ b/ant_pos/ant_pos/api/item.py
@@ -22,7 +22,7 @@ def _update_item_info(scan_result: dict[str, str | None]) -> dict[str, str | Non
return scan_result
@frappe.whitelist()
-def scan_barcode(search_value: str) -> Dict[str, Any]:
+def scan_barcode(search_value: str, search_itemname:bool) -> Dict[str, Any]:
"""Scans barcode, serial no, batch no, or item code and returns item details with HTTP status codes."""
def set_cache(data: Dict[str, Any]):
@@ -88,8 +88,18 @@ def get_cache() -> Dict[str, Any] | None:
"Item",
search_value,
["name as item_code"],
- as_dict=True,
+ as_dict=True,
)
+
+ # Search by item name only if it's search_itemname
+ if search_itemname and not item_data:
+ item_data = frappe.db.get_value(
+ "Item",
+ {"item_name": search_value},
+ ["name as item_code"],
+ as_dict=True
+ )
+
if item_data:
item_data["item"] = frappe.db.get_value("Item", item_data["item_code"], ["*"], as_dict=True)
_update_item_info(item_data)
@@ -134,7 +144,8 @@ def items(pos_profile, search_value, customer):
serial_nos = frappe.get_all(
"Serial No",
filters={"item_code": item_code, "warehouse": pos_profile_doc.warehouse},
- fields=["name as serial_no", "batch_no"]
+ fields=["name as serial_no", "batch_no"],
+ order_by="creation"
)
# If batch info is needed
@@ -154,33 +165,38 @@ def items(pos_profile, search_value, customer):
HAVING stock_qty > 0
""", (item_code, pos_profile_doc.warehouse, item_code), as_dict=True)
- # Condition 1: Check if batch exists but no matching serial no in that batch
- if has_serial_no and has_batch_no and selected_batch_no:
- serials_for_batch = any(s["batch_no"] == selected_batch_no for s in serial_nos)
- if not serials_for_batch:
- frappe.throw(_("No serial numbers found in warehouse {0} for batch {1}").format(
- pos_profile_doc.warehouse, selected_batch_no
- ))
-
# Condition 2: No batch with stock exists
if has_batch_no and not selected_batch_no:
batches_with_qty = frappe.db.sql("""
SELECT sle.batch_no
FROM `tabStock Ledger Entry` sle
+ JOIN `tabBatch` b ON sle.batch_no = b.name
WHERE sle.item_code = %s
- AND sle.warehouse = %s
- AND sle.batch_no IS NOT NULL
+ AND sle.warehouse = %s
+ AND sle.batch_no IS NOT NULL
GROUP BY sle.batch_no
HAVING SUM(sle.actual_qty) > 0
+ ORDER BY b.creation
""", (item_code, pos_profile_doc.warehouse), as_dict=True)
if not batches_with_qty:
frappe.throw(_("No batch with available stock found for item {0} in warehouse {1}").format(
item_code, pos_profile_doc.warehouse
))
+ else:
+ selected_batch_no = batches_with_qty[0].batch_no
+ # Condition 1: Check if batch exists but no matching serial no in that batch
+ if has_serial_no and has_batch_no and selected_batch_no:
+ serials_for_batch = [s["serial_no"] for s in serial_nos if s["batch_no"] == selected_batch_no]
+ if serials_for_batch:
+ if not selected_serial_no:
+ selected_serial_no = serials_for_batch[0]
+ else:
+ frappe.throw(_("No serial numbers found in warehouse {0} for batch {1}").format(
+ pos_profile_doc.warehouse, selected_batch_no
+ ))
company = frappe.db.get_value('Company', pos_profile_doc.company, ['default_currency', 'name'], as_dict=True)
-
item_args = {
"item_code": item_code,
"barcode": search_values.get("barcode"),
@@ -196,6 +212,7 @@ def items(pos_profile, search_value, customer):
"cost_center": pos_profile_doc.cost_center,
"tax_category": pos_profile_doc.tax_category,
"batch_no": selected_batch_no,
+ "serial_no":"\n".join(selected_serial_no),
"warehouse": pos_profile_doc.warehouse,
"is_pos": 1,
}
@@ -231,6 +248,7 @@ def items(pos_profile, search_value, customer):
))
item_details["serial_no"] = selected_serial_no if has_serial_no else None
item_details["serial_no_options"] = [s["serial_no"] for s in serial_nos] if has_serial_no else []
+ item_details["rate"]= item_details["price_list_rate"]
return item_details
@frappe.whitelist()
diff --git a/ant_pos/ant_pos/api/sales_invoice.py b/ant_pos/ant_pos/api/sales_invoice.py
index 4d6a0fa..e1996e9 100644
--- a/ant_pos/ant_pos/api/sales_invoice.py
+++ b/ant_pos/ant_pos/api/sales_invoice.py
@@ -19,7 +19,7 @@ def calculate_invoice_item_taxes(doc):
invoice.calculate_taxes_and_totals()
if not invoice.ignore_pricing_rule:
for item in invoice.items:
- data=get_price_list_rate_for(
+ data = get_price_list_rate_for(
{
'price_list': 'Standard Selling',
"customer": invoice.customer,
diff --git a/ant_pos/fixtures/custom_field.json b/ant_pos/fixtures/custom_field.json
index b3bcf71..e29c8a6 100644
--- a/ant_pos/fixtures/custom_field.json
+++ b/ant_pos/fixtures/custom_field.json
@@ -350,7 +350,7 @@
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
- "insert_after": "custom_edit_rate",
+ "insert_after": "custom_use_percentage_discount",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Allow Credit",
@@ -431,6 +431,60 @@
"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": "Only check this if the item name is a unique field; otherwise, it may behave unexpectedly.",
+ "docstatus": 0,
+ "doctype": "Custom Field",
+ "dt": "POS Profile",
+ "fetch_from": null,
+ "fetch_if_empty": 0,
+ "fieldname": "custom_allow_item_name_in_in_item_search",
+ "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_allow_partial_payments",
+ "is_system_generated": 0,
+ "is_virtual": 0,
+ "label": "Allow Item Name in in Item Search",
+ "length": 0,
+ "mandatory_depends_on": null,
+ "modified": "2025-09-08 10:18:17.531540",
+ "module": "Ant-Pos",
+ "name": "POS Profile-custom_allow_item_name_in_in_item_search",
+ "no_copy": 0,
+ "non_negative": 0,
+ "options": null,
+ "permlevel": 0,
+ "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,
+ "sort_options": 0,
+ "translatable": 0,
+ "unique": 0,
+ "width": null
+ },
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,