From 0b0d7c6f1abcb0a52bc44a8bce75b32f881f5837 Mon Sep 17 00:00:00 2001 From: Ali Raza Date: Sat, 30 May 2026 22:20:48 +0500 Subject: [PATCH 1/5] docs: add Cashier Settlement feature and update related documentation --- docs/features/00-overview.md | 1 + docs/features/07-draft-orders.md | 1 + docs/features/24-pos-profile-config.md | 12 +++ docs/features/25-keyboard-shortcuts.md | 13 +++ docs/features/26-cashier-settlement.md | 113 +++++++++++++++++++++++++ 5 files changed, 140 insertions(+) create mode 100644 docs/features/26-cashier-settlement.md diff --git a/docs/features/00-overview.md b/docs/features/00-overview.md index 97701fe..1ab76d1 100755 --- a/docs/features/00-overview.md +++ b/docs/features/00-overview.md @@ -32,6 +32,7 @@ | 23 | [Product Bundles](23-product-bundles.md) | Bundle items with component expansion | | 24 | [POS Profile Configuration](24-pos-profile-config.md) | Complete POS Profile settings reference | | 25 | [Keyboard Shortcuts](25-keyboard-shortcuts.md) | Full keyboard shortcut reference | +| 26 | [Cashier Settlement](26-cashier-settlement.md) | Create bills at the terminal, collect payment at a separate cashier | --- diff --git a/docs/features/07-draft-orders.md b/docs/features/07-draft-orders.md index 7a63093..1d3a69c 100755 --- a/docs/features/07-draft-orders.md +++ b/docs/features/07-draft-orders.md @@ -92,3 +92,4 @@ The dialog shows all held orders for the current shift: - Draft invoices are automatically cleaned up based on your profile settings - Use draft orders to manage busy periods efficiently - Each draft shows the customer name, item count, and total for quick identification +- Held orders and [Cashier Settlement](26-cashier-settlement.md) bills are kept separate — bills sent to a cashier do not appear in the held-orders list, and vice versa diff --git a/docs/features/24-pos-profile-config.md b/docs/features/24-pos-profile-config.md index c8a3a90..c1bfeb4 100755 --- a/docs/features/24-pos-profile-config.md +++ b/docs/features/24-pos-profile-config.md @@ -73,6 +73,8 @@ The POS Profile is the central configuration hub for X POS. It controls every as | Use POS Invoice | Use POS Invoice instead of Sales Invoice | | Allow Multi Currency | Enable multi-currency transactions | | Block Sale Beyond Available Qty | Prevent selling beyond available stock | +| Enable Cashier Settlement | Create unsettled bills at the terminal for a cashier to settle later (see [Cashier Settlement](26-cashier-settlement.md)) | +| Print Backup Receipt | Print a non-genuine backup receipt at the terminal when a bill is sent to the cashier | --- @@ -180,6 +182,16 @@ In addition to the X POS settings, the standard POS Profile provides: --- +## User Access (Applicable for Users) + +The **Applicable for Users** table assigns users to the POS Profile. X POS adds one field to each row: + +| Field | Description | +|---|---| +| Is Cashier | Allows this user to open the Cashier screen and settle bills. See [Cashier Settlement](26-cashier-settlement.md). | + +--- + ## Configuration Tips - Start with a minimal configuration and enable features as needed diff --git a/docs/features/25-keyboard-shortcuts.md b/docs/features/25-keyboard-shortcuts.md index f6e5a1b..c9b497b 100755 --- a/docs/features/25-keyboard-shortcuts.md +++ b/docs/features/25-keyboard-shortcuts.md @@ -62,6 +62,19 @@ X POS is designed for fast operation with extensive keyboard support for power u --- +## Cashier Settlement + +| Key | Action | Context | +|---|---|---| +| `Alt+0` | Open the Cashier screen | Anywhere (cashier users only) | +| `↑` (Up Arrow) | Highlight previous unsettled bill | Cashier screen | +| `↓` (Down Arrow) | Highlight next unsettled bill | Cashier screen | +| `Enter` | Open the payment dialog to settle the highlighted bill | Cashier screen | + +See [Cashier Settlement](26-cashier-settlement.md) for the full workflow. + +--- + ## Workflow Examples ### Fast Checkout (Keyboard Only) diff --git a/docs/features/26-cashier-settlement.md b/docs/features/26-cashier-settlement.md new file mode 100644 index 0000000..46a2826 --- /dev/null +++ b/docs/features/26-cashier-settlement.md @@ -0,0 +1,113 @@ +# Cashier Settlement + +Cashier Settlement separates the act of **creating a bill** from the act of **collecting payment**. A POS terminal operator builds the order and sends it to a cashier, who collects the money and closes the bill from a dedicated screen — typically on a different PC at a central cash counter. + +This mirrors how many pharmacies and high-traffic retail counters operate: sales staff ring up items at the terminal, while a single cashier handles all cash collection. + +--- + +## How It Works + +1. A POS Profile is configured with **Cashier Settlement** enabled. +2. At the terminal, the operator builds the cart as usual. The checkout button reads **Send to Cashier** instead of **Pay**. +3. Pressing **Send to Cashier** saves the order as an **unsettled draft invoice** — no payment is collected. The cart clears, ready for the next customer. +4. The unsettled invoice appears on the **Cashier** screen on any PC signed in to the same POS Profile. The list refreshes automatically. +5. The cashier selects an invoice, collects payment through the normal payment dialog, and submits it. The settled bill leaves the list, and the genuine tax invoice prints. + +Under the hood this reuses the existing draft-invoice workflow. Unsettled bills are drafts (`docstatus = 0`) flagged with a `POS Awaiting Settlement` field, which keeps them separate from ordinary [held orders](07-draft-orders.md). When a bill is settled, the invoice is re-associated with the **cashier's** open shift, so the collected amount is reconciled in the cashier's shift, not the terminal operator's. + +--- + +## Enabling the Feature + +Two settings on the **POS Profile** control the terminal behavior: + +| Setting | Description | +|---|---| +| Enable Cashier Settlement | Turns the terminal's checkout into **Send to Cashier**. No payment is collected at the terminal; the order is saved for a cashier to settle. | +| Print Backup Receipt | When enabled, the terminal prints a non-genuine **backup receipt** for the customer at the moment the order is sent. The genuine tax invoice is printed later by the cashier after settlement. (Only available when Cashier Settlement is enabled.) | + +--- + +## Cashier Access Control + +Not every user is a cashier. Access is controlled per user on the POS Profile's **Applicable for Users** table: + +| Field | Description | +|---|---| +| Is Cashier | When checked, this user may open the Cashier screen and settle (close) bills. | + +Access rules: + +- Only users with **Is Cashier** checked can open the Cashier screen or settle a bill. +- **Administrator** and any user with the **System Manager** role always have access, so managers/admins are never locked out. +- Enforcement is on the **server**, not just the UI: + - The Cashier route redirects non-cashiers back to the POS screen. + - The Cashier nav item is hidden for non-cashiers. + - The settlement and "list unsettled invoices" APIs reject non-cashiers, so a bill cannot be closed by an unauthorized user even outside the UI. + +--- + +## Terminal Workflow (Sales Operator) + +1. Open a shift and build the cart (items, customer, discounts) as normal. +2. The checkout button shows **Send to Cashier**. +3. Press it to save the unsettled bill. If **Print Backup Receipt** is enabled, a backup copy prints for the customer. +4. The cart clears immediately for the next sale. + +Returns are unaffected — return mode still uses the normal payment flow even when Cashier Settlement is enabled. + +--- + +## Cashier Workflow (Settlement Screen) + +Open the **Cashier** screen from the sidebar, or press `Alt + 0`. + +The screen lists every unsettled invoice for the POS Profile — regardless of which terminal or operator created it — and refreshes on its own every few seconds so new bills appear and settled bills disappear without manual reloading. + +Each row shows the customer, invoice reference, date/time, item count, and grand total. + +### Settling a Bill (Keyboard or Mouse) + +| Action | Result | +|---|---| +| `↑` / `↓` | Move the highlight up/down the list | +| `Enter` | Open the payment dialog for the highlighted bill | +| Mouse click | Open the payment dialog for that bill | + +In the payment dialog the cashier collects payment exactly like a normal sale. On **Save & Print**, the invoice is submitted, the genuine tax invoice prints, and the bill drops off the unsettled list. + +A manual **Refresh** button is available if needed. + +--- + +## Sales Invoice and POS Invoice Support + +Cashier Settlement works whether the system is configured for **Sales Invoice** or **POS Invoice** (set in POS Settings). + +ERPNext requires at least one mode of payment on a POS Invoice, even as a draft. Because no payment is collected at the terminal, X POS automatically seeds a zero-amount payment row on the draft so it can be saved; the cashier replaces it with the real amounts at settlement time. + +--- + +## Use Cases + +### Pharmacy / Retail Cash Counter +1. The counter staff scan items and hand the customer a backup receipt. +2. The customer walks to the cash counter and pays. +3. The cashier finds the bill on the Cashier screen, collects payment, and prints the official invoice. + +### Multiple Terminals, One Cashier +Several sales terminals can send bills to a single Cashier screen. The cashier works through the queue using the keyboard, clearing each bill as payment is collected. + +### Manager-Controlled Closing +By granting **Is Cashier** only to trusted staff, businesses ensure that bill creation (open to many operators) and money collection (restricted) are handled by different people. + +--- + +## Tips + +- The collected amount reconciles in the **cashier's** shift, so make sure the cashier has an open shift before settling. +- Use **Print Backup Receipt** so customers have something to present at the cash counter; it is clearly a draft/backup, not the tax invoice. +- The Cashier screen refreshes automatically, but the **Refresh** button forces an immediate reload. +- Ordinary [held orders](07-draft-orders.md) do **not** appear on the Cashier screen — only bills explicitly sent for settlement. +- See [POS Profile Configuration](24-pos-profile-config.md) for the related settings and [Keyboard Shortcuts](25-keyboard-shortcuts.md) for the full navigation reference. From 2d366c81e974c676accd29b1970bb4fce117ae57 Mon Sep 17 00:00:00 2001 From: Ali Raza Date: Thu, 4 Jun 2026 17:55:37 +0500 Subject: [PATCH 2/5] feat: Add POS Role and Permission management - Introduced new DocTypes: POSRole and POSRolePermission to manage roles and their associated permissions. - Implemented permission matrix in POS Role for better user experience. - Added integration tests for POSPermission and POSRole. - Updated existing POS Print Format Rule and Purchase Taxes to new file permissions. - Created POS Shift Reconciliation report for detailed shift analysis. - Enhanced workspace configuration to include new reports and functionalities. - Refactored code for better maintainability and performance. --- .prettierignore | 0 .prettierrc | 0 frontend/.yarnrc | 0 frontend/electron/database/dbService.ts | 78 ++ frontend/electron/database/schema.sql | 42 +- frontend/src/components/MenuBar.vue | 19 +- frontend/src/components/Navbar.vue | 3 +- frontend/src/components/core/BaseListView.vue | 0 .../src/components/core/ListFilterBar.vue | 0 frontend/src/components/core/ListView.vue | 0 .../src/components/core/QueryFilterPanel.vue | 0 frontend/src/components/core/SortBy.vue | 0 frontend/src/components/core/index.ts | 0 .../dialogs/BankDropDetailDialog.vue | 0 .../dialogs/CameraBarcodeDialog.vue | 0 .../src/components/dialogs/ClosingDialog.vue | 5 + .../components/dialogs/CustomerEditDialog.vue | 0 .../dialogs/ExpenseDetailDialog.vue | 0 .../components/dialogs/ExpenseFormDialog.vue | 0 .../dialogs/PurchaseInvoiceDetailDialog.vue | 0 .../dialogs/PurchaseOrderDetailDialog.vue | 0 .../dialogs/ReceiptPreviewDialog.vue | 8 +- .../dialogs/paymentDialogShortcuts.ts | 0 .../src/components/reports/MultiLinkInput.vue | 0 .../components/reports/ReportDataTable.vue | 210 ----- .../components/reports/ReportFilterBar.vue | 145 +++ .../src/components/reports/ReportTable.vue | 140 +++ .../components/ui/select/SelectComponent.vue | 0 frontend/src/components/ui/select/index.ts | 0 .../src/composables/useKeyboardShortcuts.ts | 3 + frontend/src/composables/useListView.ts | 0 frontend/src/composables/usePrintInvoice.ts | 8 +- frontend/src/router/index.ts | 4 + frontend/src/router/routes.ts | 7 + frontend/src/services/doctypeMeta.ts | 0 frontend/src/services/offerEngine.ts | 0 frontend/src/services/reportPrint.ts | 145 +++ frontend/src/services/reports.ts | 566 +++--------- frontend/src/services/userRights.ts | 49 +- frontend/src/stores/authStore.ts | 10 + frontend/src/stores/posStore.ts | 9 +- frontend/src/utils/datetime/dayjs.ts | 0 frontend/src/utils/datetime/formats.ts | 0 frontend/src/utils/datetime/index.ts | 0 frontend/src/views/CashierView.vue | 0 .../src/views/PurchaseInvoiceListView.vue | 0 frontend/src/views/PurchaseInvoiceView.vue | 0 frontend/src/views/ReportViewerView.vue | 860 ++++-------------- frontend/src/views/ReportsIndexView.vue | 87 +- frontend/src/views/RolePermissionsView.vue | 255 ++++++ frontend/tests/paymentDialogShortcuts.spec.ts | 0 xpos/api/auth.py | 411 +++++++-- xpos/api/print_formats.py | 48 + xpos/api/reports.py | 209 +++++ xpos/api/shifts.py | 8 +- xpos/api/tests/test_fbr_integration.py | 0 xpos/api/utilities.py | 14 +- xpos/desktop_icon/x_pos.json | 0 xpos/hooks.py | 11 +- xpos/install.py | 107 +++ xpos/patches.txt | 3 +- ...move_unused_pos_profile_discount_fields.py | 0 xpos/patches/seed_pos_role_permissions.py | 9 + xpos/startup/boot.py | 11 + xpos/workspace_sidebar/x_pos.json | 0 xpos/x_pos/api/item_processing/barcode.py | 2 +- .../api/test_invoice_delivery_charges.py | 0 xpos/x_pos/custom/mode_of_payment.json | 0 xpos/x_pos/custom/pos_invoice.json | 0 xpos/x_pos/custom/pos_invoice_item.json | 0 xpos/x_pos/custom/pos_profile_user.json | 168 +++- .../pos_allowed_expense_account/__init__.py | 0 .../pos_allowed_expense_account.json | 0 .../pos_allowed_expense_account.py | 0 .../pos_allowed_source_account/__init__.py | 0 .../pos_allowed_source_account.json | 0 .../pos_allowed_source_account.py | 0 .../doctype/pos_cash_movement/__init__.py | 0 .../pos_cash_movement/pos_cash_movement.js | 0 .../pos_cash_movement/pos_cash_movement.json | 0 .../pos_cash_movement/pos_cash_movement.py | 0 .../test_pos_cash_movement.py | 0 .../doctype/pos_closing_shift/__init__.py | 0 .../pos_closing_shift/pos_closing_shift.json | 0 .../pos_closing_shift/pos_closing_shift.py | 0 .../test_pos_closing_shift.py | 0 .../pos_closing_shift_detail/__init__.py | 0 .../pos_closing_shift_detail.json | 0 .../pos_closing_shift_detail.py | 0 .../pos_closing_shift_taxes/__init__.py | 0 .../pos_closing_shift_taxes.json | 0 .../pos_closing_shift_taxes.py | 0 xpos/x_pos/doctype/pos_coupon/__init__.py | 0 xpos/x_pos/doctype/pos_coupon/pos_coupon.js | 0 xpos/x_pos/doctype/pos_coupon/pos_coupon.json | 0 xpos/x_pos/doctype/pos_coupon/pos_coupon.py | 0 .../doctype/pos_coupon/test_pos_coupon.py | 0 .../doctype/pos_coupon_detail/__init__.py | 0 .../pos_coupon_detail/pos_coupon_detail.json | 0 .../pos_coupon_detail/pos_coupon_detail.py | 0 xpos/x_pos/doctype/pos_offer/__init__.py | 0 xpos/x_pos/doctype/pos_offer/pos_offer.js | 0 xpos/x_pos/doctype/pos_offer/pos_offer.json | 0 xpos/x_pos/doctype/pos_offer/pos_offer.py | 0 .../x_pos/doctype/pos_offer/test_pos_offer.py | 0 .../doctype/pos_offer_detail/__init__.py | 0 .../pos_offer_detail/pos_offer_detail.json | 0 .../pos_offer_detail/pos_offer_detail.py | 0 .../doctype/pos_opening_shift/__init__.py | 0 .../pos_opening_shift/pos_opening_shift.js | 0 .../pos_opening_shift/pos_opening_shift.json | 0 .../pos_opening_shift/pos_opening_shift.py | 0 .../test_pos_opening_shift.py | 0 .../pos_opening_shift_detail/__init__.py | 0 .../pos_opening_shift_detail.json | 0 .../pos_opening_shift_detail.py | 0 .../pos_payment_entry_reference/__init__.py | 0 .../pos_payment_entry_reference.json | 0 .../pos_payment_entry_reference.py | 0 xpos/x_pos/doctype/pos_permission/__init__.py | 0 .../doctype/pos_permission/pos_permission.js | 8 + .../pos_permission/pos_permission.json | 67 ++ .../doctype/pos_permission/pos_permission.py | 27 + .../pos_permission/test_pos_permission.py | 22 + .../doctype/pos_print_format_rule/__init__.py | 0 .../pos_print_format_rule.json | 0 .../pos_print_format_rule.py | 0 .../doctype/pos_purchase_taxes/__init__.py | 0 .../pos_purchase_taxes.json | 0 .../pos_purchase_taxes/pos_purchase_taxes.py | 0 xpos/x_pos/doctype/pos_role/__init__.py | 0 xpos/x_pos/doctype/pos_role/pos_role.js | 139 +++ xpos/x_pos/doctype/pos_role/pos_role.json | 74 ++ xpos/x_pos/doctype/pos_role/pos_role.py | 51 ++ xpos/x_pos/doctype/pos_role/test_pos_role.py | 22 + .../doctype/pos_role_permission/__init__.py | 0 .../pos_role_permission.json | 48 + .../pos_role_permission.py | 24 + .../pos_sales_person_filter/__init__.py | 0 .../pos_sales_person_filter.json | 0 .../pos_sales_person_filter.py | 0 xpos/x_pos/integrations/__init__.py | 0 xpos/x_pos/integrations/fbr.py | 0 .../pos_shift_reconciliation/__init__.py | 0 .../pos_shift_reconciliation.js | 38 + .../pos_shift_reconciliation.json | 34 + .../pos_shift_reconciliation.py | 140 +++ .../report/stock_audit_report/__init__.py | 0 .../stock_audit_report/stock_audit_report.js | 0 .../stock_audit_report.json | 0 .../stock_audit_report/stock_audit_report.py | 0 xpos/x_pos/workspace/x_pos/x_pos.json | 231 ++++- 152 files changed, 3034 insertions(+), 1545 deletions(-) mode change 100644 => 100755 .prettierignore mode change 100644 => 100755 .prettierrc mode change 100644 => 100755 frontend/.yarnrc mode change 100644 => 100755 frontend/src/components/core/BaseListView.vue mode change 100644 => 100755 frontend/src/components/core/ListFilterBar.vue mode change 100644 => 100755 frontend/src/components/core/ListView.vue mode change 100644 => 100755 frontend/src/components/core/QueryFilterPanel.vue mode change 100644 => 100755 frontend/src/components/core/SortBy.vue mode change 100644 => 100755 frontend/src/components/core/index.ts mode change 100644 => 100755 frontend/src/components/dialogs/BankDropDetailDialog.vue mode change 100644 => 100755 frontend/src/components/dialogs/CameraBarcodeDialog.vue mode change 100644 => 100755 frontend/src/components/dialogs/CustomerEditDialog.vue mode change 100644 => 100755 frontend/src/components/dialogs/ExpenseDetailDialog.vue mode change 100644 => 100755 frontend/src/components/dialogs/ExpenseFormDialog.vue mode change 100644 => 100755 frontend/src/components/dialogs/PurchaseInvoiceDetailDialog.vue mode change 100644 => 100755 frontend/src/components/dialogs/PurchaseOrderDetailDialog.vue mode change 100644 => 100755 frontend/src/components/dialogs/ReceiptPreviewDialog.vue mode change 100644 => 100755 frontend/src/components/dialogs/paymentDialogShortcuts.ts mode change 100644 => 100755 frontend/src/components/reports/MultiLinkInput.vue delete mode 100644 frontend/src/components/reports/ReportDataTable.vue create mode 100755 frontend/src/components/reports/ReportFilterBar.vue create mode 100755 frontend/src/components/reports/ReportTable.vue mode change 100644 => 100755 frontend/src/components/ui/select/SelectComponent.vue mode change 100644 => 100755 frontend/src/components/ui/select/index.ts mode change 100644 => 100755 frontend/src/composables/useListView.ts mode change 100644 => 100755 frontend/src/composables/usePrintInvoice.ts mode change 100644 => 100755 frontend/src/services/doctypeMeta.ts mode change 100644 => 100755 frontend/src/services/offerEngine.ts create mode 100755 frontend/src/services/reportPrint.ts mode change 100644 => 100755 frontend/src/services/reports.ts mode change 100644 => 100755 frontend/src/utils/datetime/dayjs.ts mode change 100644 => 100755 frontend/src/utils/datetime/formats.ts mode change 100644 => 100755 frontend/src/utils/datetime/index.ts mode change 100644 => 100755 frontend/src/views/CashierView.vue mode change 100644 => 100755 frontend/src/views/PurchaseInvoiceListView.vue mode change 100644 => 100755 frontend/src/views/PurchaseInvoiceView.vue mode change 100644 => 100755 frontend/src/views/ReportViewerView.vue mode change 100644 => 100755 frontend/src/views/ReportsIndexView.vue create mode 100755 frontend/src/views/RolePermissionsView.vue mode change 100644 => 100755 frontend/tests/paymentDialogShortcuts.spec.ts create mode 100755 xpos/api/reports.py mode change 100644 => 100755 xpos/api/tests/test_fbr_integration.py mode change 100644 => 100755 xpos/desktop_icon/x_pos.json create mode 100755 xpos/install.py mode change 100644 => 100755 xpos/patches/remove_unused_pos_profile_discount_fields.py create mode 100644 xpos/patches/seed_pos_role_permissions.py mode change 100644 => 100755 xpos/workspace_sidebar/x_pos.json mode change 100644 => 100755 xpos/x_pos/api/test_invoice_delivery_charges.py mode change 100644 => 100755 xpos/x_pos/custom/mode_of_payment.json mode change 100644 => 100755 xpos/x_pos/custom/pos_invoice.json mode change 100644 => 100755 xpos/x_pos/custom/pos_invoice_item.json mode change 100644 => 100755 xpos/x_pos/custom/pos_profile_user.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_allowed_expense_account/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_allowed_expense_account/pos_allowed_expense_account.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_allowed_expense_account/pos_allowed_expense_account.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_allowed_source_account/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_allowed_source_account/pos_allowed_source_account.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_allowed_source_account/pos_allowed_source_account.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_cash_movement/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_cash_movement/pos_cash_movement.js mode change 100644 => 100755 xpos/x_pos/doctype/pos_cash_movement/pos_cash_movement.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_cash_movement/pos_cash_movement.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_cash_movement/test_pos_cash_movement.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_closing_shift/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_closing_shift/pos_closing_shift.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_closing_shift/pos_closing_shift.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_closing_shift/test_pos_closing_shift.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_closing_shift_detail/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_closing_shift_detail/pos_closing_shift_detail.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_closing_shift_detail/pos_closing_shift_detail.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_closing_shift_taxes/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_closing_shift_taxes/pos_closing_shift_taxes.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_closing_shift_taxes/pos_closing_shift_taxes.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_coupon/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_coupon/pos_coupon.js mode change 100644 => 100755 xpos/x_pos/doctype/pos_coupon/pos_coupon.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_coupon/pos_coupon.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_coupon/test_pos_coupon.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_coupon_detail/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_coupon_detail/pos_coupon_detail.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_coupon_detail/pos_coupon_detail.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_offer/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_offer/pos_offer.js mode change 100644 => 100755 xpos/x_pos/doctype/pos_offer/pos_offer.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_offer/pos_offer.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_offer/test_pos_offer.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_offer_detail/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_offer_detail/pos_offer_detail.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_offer_detail/pos_offer_detail.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_opening_shift/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_opening_shift/pos_opening_shift.js mode change 100644 => 100755 xpos/x_pos/doctype/pos_opening_shift/pos_opening_shift.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_opening_shift/pos_opening_shift.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_opening_shift/test_pos_opening_shift.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_opening_shift_detail/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_opening_shift_detail/pos_opening_shift_detail.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_opening_shift_detail/pos_opening_shift_detail.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_payment_entry_reference/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_payment_entry_reference/pos_payment_entry_reference.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_payment_entry_reference/pos_payment_entry_reference.py create mode 100755 xpos/x_pos/doctype/pos_permission/__init__.py create mode 100755 xpos/x_pos/doctype/pos_permission/pos_permission.js create mode 100755 xpos/x_pos/doctype/pos_permission/pos_permission.json create mode 100755 xpos/x_pos/doctype/pos_permission/pos_permission.py create mode 100755 xpos/x_pos/doctype/pos_permission/test_pos_permission.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_print_format_rule/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_print_format_rule/pos_print_format_rule.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_print_format_rule/pos_print_format_rule.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_purchase_taxes/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_purchase_taxes/pos_purchase_taxes.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_purchase_taxes/pos_purchase_taxes.py create mode 100755 xpos/x_pos/doctype/pos_role/__init__.py create mode 100755 xpos/x_pos/doctype/pos_role/pos_role.js create mode 100755 xpos/x_pos/doctype/pos_role/pos_role.json create mode 100755 xpos/x_pos/doctype/pos_role/pos_role.py create mode 100755 xpos/x_pos/doctype/pos_role/test_pos_role.py create mode 100755 xpos/x_pos/doctype/pos_role_permission/__init__.py create mode 100755 xpos/x_pos/doctype/pos_role_permission/pos_role_permission.json create mode 100755 xpos/x_pos/doctype/pos_role_permission/pos_role_permission.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_sales_person_filter/__init__.py mode change 100644 => 100755 xpos/x_pos/doctype/pos_sales_person_filter/pos_sales_person_filter.json mode change 100644 => 100755 xpos/x_pos/doctype/pos_sales_person_filter/pos_sales_person_filter.py mode change 100644 => 100755 xpos/x_pos/integrations/__init__.py mode change 100644 => 100755 xpos/x_pos/integrations/fbr.py create mode 100755 xpos/x_pos/report/pos_shift_reconciliation/__init__.py create mode 100755 xpos/x_pos/report/pos_shift_reconciliation/pos_shift_reconciliation.js create mode 100755 xpos/x_pos/report/pos_shift_reconciliation/pos_shift_reconciliation.json create mode 100755 xpos/x_pos/report/pos_shift_reconciliation/pos_shift_reconciliation.py mode change 100644 => 100755 xpos/x_pos/report/stock_audit_report/__init__.py mode change 100644 => 100755 xpos/x_pos/report/stock_audit_report/stock_audit_report.js mode change 100644 => 100755 xpos/x_pos/report/stock_audit_report/stock_audit_report.json mode change 100644 => 100755 xpos/x_pos/report/stock_audit_report/stock_audit_report.py diff --git a/.prettierignore b/.prettierignore old mode 100644 new mode 100755 diff --git a/.prettierrc b/.prettierrc old mode 100644 new mode 100755 diff --git a/frontend/.yarnrc b/frontend/.yarnrc old mode 100644 new mode 100755 diff --git a/frontend/electron/database/dbService.ts b/frontend/electron/database/dbService.ts index 12c0488..e9ea46d 100755 --- a/frontend/electron/database/dbService.ts +++ b/frontend/electron/database/dbService.ts @@ -245,6 +245,84 @@ async function runMigrations(): Promise { log.warn(`Migration for pos_profiles.${col} failed`, err); } } + + const posUserMigrations: [string, string][] = [ + ["close_bill", "TINYINT(1) DEFAULT 1"], + ["close_shift", "TINYINT(1) DEFAULT 0"], + ["allow_reprint_invoice", "TINYINT(1) DEFAULT 0"], + ["shift_report", "TINYINT(1) DEFAULT 0"], + ["allow_cancel_invoice", "TINYINT(1) DEFAULT 0"], + ["unsettled_invoices", "TINYINT(1) DEFAULT 0"], + ["apply_additional_discount", "TINYINT(1) DEFAULT 0"], + ["apply_standard_discount", "TINYINT(1) DEFAULT 0"], + ["show_edit_discount_field", "TINYINT(1) DEFAULT 0"], + ["edit_tax_template", "TINYINT(1) DEFAULT 0"], + ["allow_change_price", "TINYINT(1) DEFAULT 0"], + ["quotation", "TINYINT(1) DEFAULT 0"], + ["sale_return", "TINYINT(1) DEFAULT 0"], + ["local_purchase", "TINYINT(1) DEFAULT 0"], + ["purchase_order", "TINYINT(1) DEFAULT 0"], + ["purchase_invoice", "TINYINT(1) DEFAULT 0"], + ["stock_adjustment", "TINYINT(1) DEFAULT 0"], + ["stock_entry", "TINYINT(1) DEFAULT 0"], + ["near_expiry_items", "TINYINT(1) DEFAULT 0"], + ["expense", "TINYINT(1) DEFAULT 0"], + ["bank_drop", "TINYINT(1) DEFAULT 0"], + ["list_of_invoices", "TINYINT(1) DEFAULT 1"], + ["list_of_cancelled_invoices", "TINYINT(1) DEFAULT 0"], + ["list_of_errors", "TINYINT(1) DEFAULT 0"], + ["list_of_purchase_invoices", "TINYINT(1) DEFAULT 0"], + ["list_of_quotations", "TINYINT(1) DEFAULT 0"], + ["list_of_stock_entries", "TINYINT(1) DEFAULT 0"], + ["list_of_local_purchases", "TINYINT(1) DEFAULT 0"], + ["list_of_stock_adjustments", "TINYINT(1) DEFAULT 0"], + ["list_of_expense", "TINYINT(1) DEFAULT 0"], + ["list_of_bank_drops", "TINYINT(1) DEFAULT 0"], + ["invoice_settlement_report", "TINYINT(1) DEFAULT 0"], + ["sales_report_by_time", "TINYINT(1) DEFAULT 0"], + ["sales_summary_by_hour", "TINYINT(1) DEFAULT 0"], + ["current_stock_by_brand", "TINYINT(1) DEFAULT 0"], + ["stock_register", "TINYINT(1) DEFAULT 0"], + ["current_stock_report", "TINYINT(1) DEFAULT 0"], + ["discount_limit", "DECIMAL(18,6) DEFAULT 100"], + ]; + + const posUserColumnExists = async (col: string): Promise => { + const [existing] = await db.execute( + "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'pos_users' AND COLUMN_NAME = ?", + [col], + ); + return (existing as RowDataPacket[]).length > 0; + }; + + for (const [col, typedef] of posUserMigrations) { + try { + if (!(await posUserColumnExists(col))) { + await db.execute(`ALTER TABLE \`pos_users\` ADD COLUMN \`${col}\` ${typedef}`); + log.info(`Migration: added pos_users.${col}`); + } + } catch (err) { + log.warn(`Migration for pos_users.${col} failed`, err); + } + } + + const posUserRenames: [string, string][] = [ + ["allow_return", "sale_return"], + ["allow_expense", "expense"], + ["allow_bank_drop", "bank_drop"], + ["show_edit_item_tax_template", "edit_tax_template"], + ]; + for (const [oldCol, newCol] of posUserRenames) { + try { + if (await posUserColumnExists(oldCol)) { + await db.execute(`UPDATE \`pos_users\` SET \`${newCol}\` = \`${oldCol}\``); + await db.execute(`ALTER TABLE \`pos_users\` DROP COLUMN \`${oldCol}\``); + log.info(`Migration: renamed pos_users.${oldCol} -> ${newCol}`); + } + } catch (err) { + log.warn(`Migration for pos_users rename ${oldCol} -> ${newCol} failed`, err); + } + } } async function executeSchemaFile(filePath: string): Promise { diff --git a/frontend/electron/database/schema.sql b/frontend/electron/database/schema.sql index c874ab8..73e1c10 100755 --- a/frontend/electron/database/schema.sql +++ b/frontend/electron/database/schema.sql @@ -473,20 +473,44 @@ CREATE TABLE IF NOT EXISTS `pos_users` ( `company` VARCHAR(255) DEFAULT NULL, `theme` VARCHAR(50) DEFAULT 'Default', `enabled` TINYINT(1) DEFAULT 1, + `discount_limit` DECIMAL(18,6) DEFAULT 100, `close_bill` TINYINT(1) DEFAULT 1, + `close_shift` TINYINT(1) DEFAULT 0, `allow_reprint_invoice` TINYINT(1) DEFAULT 0, - `stock_adjustment` TINYINT(1) DEFAULT 0, - `quotation` TINYINT(1) DEFAULT 0, + `shift_report` TINYINT(1) DEFAULT 0, + `allow_cancel_invoice` TINYINT(1) DEFAULT 0, + `unsettled_invoices` TINYINT(1) DEFAULT 0, `apply_additional_discount` TINYINT(1) DEFAULT 0, `apply_standard_discount` TINYINT(1) DEFAULT 0, - `allow_change_price` TINYINT(1) DEFAULT 0, `show_edit_discount_field` TINYINT(1) DEFAULT 0, - `show_edit_item_tax_template` TINYINT(1) DEFAULT 0, - `allow_return` TINYINT(1) DEFAULT 0, - `allow_cancel_invoice` TINYINT(1) DEFAULT 0, - `allow_expense` TINYINT(1) DEFAULT 0, - `allow_bank_drop` TINYINT(1) DEFAULT 0, - `discount_limit` DECIMAL(18,6) DEFAULT 100, + `edit_tax_template` TINYINT(1) DEFAULT 0, + `allow_change_price` TINYINT(1) DEFAULT 0, + `quotation` TINYINT(1) DEFAULT 0, + `sale_return` TINYINT(1) DEFAULT 0, + `local_purchase` TINYINT(1) DEFAULT 0, + `purchase_order` TINYINT(1) DEFAULT 0, + `purchase_invoice` TINYINT(1) DEFAULT 0, + `stock_adjustment` TINYINT(1) DEFAULT 0, + `stock_entry` TINYINT(1) DEFAULT 0, + `near_expiry_items` TINYINT(1) DEFAULT 0, + `expense` TINYINT(1) DEFAULT 0, + `bank_drop` TINYINT(1) DEFAULT 0, + `list_of_invoices` TINYINT(1) DEFAULT 1, + `list_of_cancelled_invoices` TINYINT(1) DEFAULT 0, + `list_of_errors` TINYINT(1) DEFAULT 0, + `list_of_purchase_invoices` TINYINT(1) DEFAULT 0, + `list_of_quotations` TINYINT(1) DEFAULT 0, + `list_of_stock_entries` TINYINT(1) DEFAULT 0, + `list_of_local_purchases` TINYINT(1) DEFAULT 0, + `list_of_stock_adjustments` TINYINT(1) DEFAULT 0, + `list_of_expense` TINYINT(1) DEFAULT 0, + `list_of_bank_drops` TINYINT(1) DEFAULT 0, + `invoice_settlement_report` TINYINT(1) DEFAULT 0, + `sales_report_by_time` TINYINT(1) DEFAULT 0, + `sales_summary_by_hour` TINYINT(1) DEFAULT 0, + `current_stock_by_brand` TINYINT(1) DEFAULT 0, + `stock_register` TINYINT(1) DEFAULT 0, + `current_stock_report` TINYINT(1) DEFAULT 0, `modified` DATETIME DEFAULT NULL, `synced_at` DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE INDEX `idx_username` (`username`), diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index 159b026..674d2f4 100755 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -26,7 +26,8 @@ class="absolute top-full start-0 min-w-[220px] bg-[#252526] dark:bg-[#252526] border border-[#454545] shadow-2xl py-1 z-[200]" >