Showing preview only (1,787K chars total). Download the full file or copy to clipboard to get everything.
Repository: ury-erp/ury
Branch: develop
Commit: acdbb57eabae
Files: 391
Total size: 1.6 MB
Directory structure:
gitextract_j3w6bb6w/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── AGENTS.MD
├── CLAUDE.MD
├── FEATURES.md
├── INSTALLATION.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── SETUP.md
├── TERMS.md
├── URYMosaic/
│ ├── .gitignore
│ ├── .vscode/
│ │ └── extensions.json
│ ├── AGENTS.MD
│ ├── CLAUDE.MD
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── proxyOptions.js
│ ├── src/
│ │ ├── App.vue
│ │ ├── components/
│ │ │ ├── Header.vue
│ │ │ └── kot.vue
│ │ ├── index.css
│ │ ├── main.js
│ │ ├── router/
│ │ │ ├── auth.js
│ │ │ └── index.js
│ │ ├── style.css
│ │ └── views/
│ │ ├── Home.vue
│ │ └── Login.vue
│ ├── tailwind.config.js
│ └── vite.config.js
├── package.json
├── pos/
│ ├── .gitignore
│ ├── AGENTS.MD
│ ├── CLAUDE.MD
│ ├── README.md
│ ├── eslint.config.js
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── privateKey.js
│ ├── src/
│ │ ├── App.tsx
│ │ ├── components/
│ │ │ ├── AggregatorSelect.tsx
│ │ │ ├── AuthGuard.tsx
│ │ │ ├── CommentDialog.tsx
│ │ │ ├── CustomerSelect.tsx
│ │ │ ├── Footer.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── InitialLoader.tsx
│ │ │ ├── LayoutView.tsx
│ │ │ ├── MenuCard.tsx
│ │ │ ├── MenuList.tsx
│ │ │ ├── OrderPanel.tsx
│ │ │ ├── OrderStatusSidebar.tsx
│ │ │ ├── OrderTypeSelect.tsx
│ │ │ ├── POSOpeningDialog.tsx
│ │ │ ├── POSOpeningProvider.tsx
│ │ │ ├── PaymentDialog.tsx
│ │ │ ├── ProductDialog.tsx
│ │ │ ├── ScreenSizeDialog.tsx
│ │ │ ├── ScreenSizeProvider.tsx
│ │ │ ├── SearchBar.tsx
│ │ │ ├── Sidebar.tsx
│ │ │ ├── Spotlight.tsx
│ │ │ ├── TableSelectionDialog.tsx
│ │ │ ├── TableShapeIcon.tsx
│ │ │ └── ui/
│ │ │ ├── README.md
│ │ │ ├── badge.tsx
│ │ │ ├── button.tsx
│ │ │ ├── card.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── example.tsx
│ │ │ ├── index.ts
│ │ │ ├── input.tsx
│ │ │ ├── loader.tsx
│ │ │ ├── select.tsx
│ │ │ ├── spinner.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── toast.css
│ │ │ └── toast.tsx
│ │ ├── data/
│ │ │ ├── doctypes.ts
│ │ │ ├── menu-data.ts
│ │ │ └── order-types.ts
│ │ ├── i18n/
│ │ │ ├── config.ts
│ │ │ ├── index.ts
│ │ │ ├── loader.ts
│ │ │ ├── locales/
│ │ │ │ ├── ar.json
│ │ │ │ ├── en.json
│ │ │ │ └── fr.json
│ │ │ └── resolve-language.ts
│ │ ├── index.css
│ │ ├── lib/
│ │ │ ├── aggregator-api.ts
│ │ │ ├── auth-api.ts
│ │ │ ├── customer-api.ts
│ │ │ ├── frappe-sdk.ts
│ │ │ ├── invoice-api.ts
│ │ │ ├── menu-api.ts
│ │ │ ├── menu-course-api.ts
│ │ │ ├── order-api.ts
│ │ │ ├── payment-api.ts
│ │ │ ├── pos-opening-api.ts
│ │ │ ├── pos-profile-api.ts
│ │ │ ├── print-qz.ts
│ │ │ ├── print.ts
│ │ │ ├── role-utils.ts
│ │ │ ├── storage.ts
│ │ │ ├── table-api.ts
│ │ │ └── utils.ts
│ │ ├── main.tsx
│ │ ├── pages/
│ │ │ ├── Orders.tsx
│ │ │ ├── POS.tsx
│ │ │ └── Table.tsx
│ │ ├── store/
│ │ │ ├── pos-store.ts
│ │ │ ├── root-store.ts
│ │ │ └── slices/
│ │ │ ├── auth-slice.ts
│ │ │ ├── config-slice.ts
│ │ │ └── orders-slice.ts
│ │ └── vite-env.d.ts
│ ├── tailwind.config.js
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── pyproject.toml
├── requirements.txt
├── setup.py
├── ury/
│ ├── __init__.py
│ ├── config/
│ │ ├── __init__.py
│ │ ├── desktop.py
│ │ └── docs.py
│ ├── fixtures/
│ │ ├── client_script.json
│ │ ├── custom_field.json
│ │ ├── custom_html_block.json
│ │ ├── property_setter.json
│ │ └── role.json
│ ├── hooks.py
│ ├── install.py
│ ├── modules.txt
│ ├── patches/
│ │ └── v2_0/
│ │ └── default_permissions.py
│ ├── patches.txt
│ ├── permission.py
│ ├── public/
│ │ ├── .gitkeep
│ │ ├── images
│ │ └── js/
│ │ ├── jsrsasign-all-min.js
│ │ ├── pos_extend.js
│ │ ├── pos_print.js
│ │ ├── quick_entry.js
│ │ ├── qz-tray.js
│ │ ├── restrict_qty_edit_pos.js
│ │ ├── sign-message.js
│ │ └── ury_pos_kot.js
│ ├── setup.py
│ ├── templates/
│ │ ├── __init__.py
│ │ └── pages/
│ │ └── __init__.py
│ ├── uninstall.py
│ ├── ury/
│ │ ├── __init__.py
│ │ ├── api/
│ │ │ ├── button_permission.py
│ │ │ ├── pos_extend.py
│ │ │ ├── ury_kot_display.py
│ │ │ ├── ury_kot_generate.py
│ │ │ ├── ury_kot_notification.py
│ │ │ ├── ury_kot_order_number.py
│ │ │ ├── ury_kot_reprint.py
│ │ │ ├── ury_kot_validation.py
│ │ │ ├── ury_menu_course_validation.py
│ │ │ └── ury_print.py
│ │ ├── custom/
│ │ │ └── item.json
│ │ ├── doctype/
│ │ │ ├── __init__.py
│ │ │ ├── aggregator_settings/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── aggregator_settings.json
│ │ │ │ └── aggregator_settings.py
│ │ │ ├── item_add_on/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── item_add_on.json
│ │ │ │ └── item_add_on.py
│ │ │ ├── menu_for_room/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── menu_for_room.js
│ │ │ │ ├── menu_for_room.json
│ │ │ │ ├── menu_for_room.py
│ │ │ │ └── test_menu_for_room.py
│ │ │ ├── multiple_rooms/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── multiple_rooms.json
│ │ │ │ └── multiple_rooms.py
│ │ │ ├── order_type_menu/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── order_type_menu.json
│ │ │ │ └── order_type_menu.py
│ │ │ ├── pos_item_variants/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── pos_item_variants.json
│ │ │ │ └── pos_item_variants.py
│ │ │ ├── role_permitted/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── role_permitted.json
│ │ │ │ └── role_permitted.py
│ │ │ ├── sub_pos_closing/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── sub_pos_closing.js
│ │ │ │ ├── sub_pos_closing.json
│ │ │ │ ├── sub_pos_closing.py
│ │ │ │ └── test_sub_pos_closing.py
│ │ │ ├── sub_pos_closing_payment/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── sub_pos_closing_payment.json
│ │ │ │ └── sub_pos_closing_payment.py
│ │ │ ├── sub_pos_invoices/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── sub_pos_invoices.json
│ │ │ │ └── sub_pos_invoices.py
│ │ │ ├── ury_cost_of_goods/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_cost_of_goods.json
│ │ │ │ └── ury_cost_of_goods.py
│ │ │ ├── ury_daily_p_and_l/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── profit_loss_details.html
│ │ │ │ ├── test_ury_daily_p_and_l.py
│ │ │ │ ├── ury_daily_p_and_l.js
│ │ │ │ ├── ury_daily_p_and_l.json
│ │ │ │ └── ury_daily_p_and_l.py
│ │ │ ├── ury_fixed_expenses/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_fixed_expenses.json
│ │ │ │ └── ury_fixed_expenses.py
│ │ │ ├── ury_kot/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_kot.py
│ │ │ │ ├── ury_kot.js
│ │ │ │ ├── ury_kot.json
│ │ │ │ └── ury_kot.py
│ │ │ ├── ury_kot_error_log/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_kot_error_log.py
│ │ │ │ ├── ury_kot_error_log.js
│ │ │ │ ├── ury_kot_error_log.json
│ │ │ │ └── ury_kot_error_log.py
│ │ │ ├── ury_kot_items/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_kot_items.json
│ │ │ │ └── ury_kot_items.py
│ │ │ ├── ury_materials/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_materials.json
│ │ │ │ └── ury_materials.py
│ │ │ ├── ury_menu/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_menu.py
│ │ │ │ ├── ury_menu.js
│ │ │ │ ├── ury_menu.json
│ │ │ │ └── ury_menu.py
│ │ │ ├── ury_menu_course/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_menu_course.py
│ │ │ │ ├── ury_menu_course.js
│ │ │ │ ├── ury_menu_course.json
│ │ │ │ └── ury_menu_course.py
│ │ │ ├── ury_menu_item/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_menu_item.json
│ │ │ │ └── ury_menu_item.py
│ │ │ ├── ury_notification_recipient/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_notification_recipient.py
│ │ │ │ ├── ury_notification_recipient.js
│ │ │ │ ├── ury_notification_recipient.json
│ │ │ │ └── ury_notification_recipient.py
│ │ │ ├── ury_order/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_order.py
│ │ │ │ ├── ury_order.js
│ │ │ │ ├── ury_order.json
│ │ │ │ └── ury_order.py
│ │ │ ├── ury_order_item/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_order_item.py
│ │ │ │ ├── ury_order_item.js
│ │ │ │ ├── ury_order_item.json
│ │ │ │ └── ury_order_item.py
│ │ │ ├── ury_p_and_l_breakup/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_p_and_l_breakup.json
│ │ │ │ └── ury_p_and_l_breakup.py
│ │ │ ├── ury_p_and_l_materials/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_p_and_l_materials.json
│ │ │ │ └── ury_p_and_l_materials.py
│ │ │ ├── ury_printer_settings/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_printer_settings.json
│ │ │ │ └── ury_printer_settings.py
│ │ │ ├── ury_production_item_groups/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_production_item_groups.json
│ │ │ │ └── ury_production_item_groups.py
│ │ │ ├── ury_production_unit/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_production_unit.py
│ │ │ │ ├── ury_production_unit.js
│ │ │ │ ├── ury_production_unit.json
│ │ │ │ └── ury_production_unit.py
│ │ │ ├── ury_report_settings/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_report_settings.py
│ │ │ │ ├── ury_report_settings.js
│ │ │ │ ├── ury_report_settings.json
│ │ │ │ └── ury_report_settings.py
│ │ │ ├── ury_restaurant/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_restaurant.py
│ │ │ │ ├── ury_restaurant.js
│ │ │ │ ├── ury_restaurant.json
│ │ │ │ └── ury_restaurant.py
│ │ │ ├── ury_room/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_room.py
│ │ │ │ ├── ury_room.js
│ │ │ │ ├── ury_room.json
│ │ │ │ └── ury_room.py
│ │ │ ├── ury_table/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_table.py
│ │ │ │ ├── ury_table.js
│ │ │ │ ├── ury_table.json
│ │ │ │ └── ury_table.py
│ │ │ ├── ury_user/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_user.json
│ │ │ │ └── ury_user.py
│ │ │ └── ury_variable_expenses/
│ │ │ ├── __init__.py
│ │ │ ├── ury_variable_expenses.json
│ │ │ └── ury_variable_expenses.py
│ │ ├── hooks/
│ │ │ ├── ury_item.py
│ │ │ ├── ury_pos_closing_entry.py
│ │ │ ├── ury_pos_invoice.py
│ │ │ ├── ury_pos_opening_entry.py
│ │ │ ├── ury_pos_profile.py
│ │ │ └── ury_sales_invoice.py
│ │ ├── page/
│ │ │ ├── __init__.py
│ │ │ └── websocket_print/
│ │ │ ├── __init__.py
│ │ │ ├── websocket_print.js
│ │ │ └── websocket_print.json
│ │ ├── report/
│ │ │ ├── __init__.py
│ │ │ ├── average_bill_value/
│ │ │ │ ├── __init__.py
│ │ │ │ └── average_bill_value.json
│ │ │ ├── cancelled_invoices/
│ │ │ │ ├── __init__.py
│ │ │ │ └── cancelled_invoices.json
│ │ │ ├── customer_data/
│ │ │ │ ├── __init__.py
│ │ │ │ └── customer_data.json
│ │ │ ├── daywise_customer_details/
│ │ │ │ ├── __init__.py
│ │ │ │ └── daywise_customer_details.json
│ │ │ ├── daywise_invoices/
│ │ │ │ ├── __init__.py
│ │ │ │ └── daywise_invoices.json
│ │ │ ├── daywise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── daywise_sales.json
│ │ │ ├── employee_item_wise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── employee_item_wise_sales.json
│ │ │ ├── employee_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── employee_sales.json
│ │ │ ├── item_wise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── item_wise_sales.json
│ │ │ ├── month_wise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── month_wise_sales.json
│ │ │ ├── repeated_customers/
│ │ │ │ ├── __init__.py
│ │ │ │ └── repeated_customers.json
│ │ │ ├── service_wise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── service_wise_sales.json
│ │ │ ├── time_wise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── time_wise_sales.json
│ │ │ └── today's_sales/
│ │ │ ├── __init__.py
│ │ │ └── today's_sales.json
│ │ └── workspace/
│ │ └── ury/
│ │ └── ury.json
│ ├── ury_pos/
│ │ └── api.py
│ └── www/
│ ├── __init__.py
│ └── pos.py
└── urypos/
├── .gitignore
├── .vscode/
│ └── extensions.json
├── README.md
├── index.html
├── package.json
├── postcss.config.js
├── privateKey.js
├── proxyOptions.js
├── src/
│ ├── App.vue
│ ├── components/
│ │ ├── Cart.vue
│ │ ├── Customer.vue
│ │ ├── Header.vue
│ │ ├── Login.vue
│ │ ├── Menu.vue
│ │ ├── NotificationModal.vue
│ │ ├── Search.vue
│ │ ├── Table.vue
│ │ ├── bottomTabs.vue
│ │ ├── orderInfo.vue
│ │ ├── posClosing.vue
│ │ ├── posOpening.vue
│ │ ├── recentOrder.vue
│ │ └── takeAwayTable.vue
│ ├── index.css
│ ├── main.js
│ ├── router/
│ │ ├── auth.js
│ │ └── index.js
│ ├── stores/
│ │ ├── Alert.js
│ │ ├── Auth.js
│ │ ├── Customer.js
│ │ ├── Menu.js
│ │ ├── Notification.js
│ │ ├── NotificationModal.js
│ │ ├── Table.js
│ │ ├── bottomTabs.js
│ │ ├── frappeSdk.js
│ │ ├── invoiceData.js
│ │ ├── posClosing.js
│ │ ├── posOpening.js
│ │ ├── recentOrder.js
│ │ └── utils/
│ │ └── PrintWithQz.js
│ ├── style.css
│ └── views/
│ ├── Home.vue
│ └── Login.vue
├── tailwind.config.js
└── vite.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches:
- develop
pull_request:
concurrency:
group: develop-ury-${{ github.event.number }}
cancel-in-progress: true
jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
name: Server
services:
mariadb:
image: mariadb:10.6
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 14
check-latest: true
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py', '**/setup.cfg') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: 'echo "::set-output name=dir::$(yarn cache dir)"'
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Setup
run: |
pip install frappe-bench
bench init --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- name: Install
working-directory: /home/runner/frappe-bench
run: |
bench get-app ury $GITHUB_WORKSPACE
bench setup requirements --dev
bench new-site --db-root-password root --admin-password admin test_site
bench --site test_site install-app ury
bench build
env:
CI: 'Yes'
- name: Run Tests
working-directory: /home/runner/frappe-bench
run: |
bench --site test_site set-config allow_tests true
bench --site test_site run-tests --app ury
env:
TYPE: server
================================================
FILE: .gitignore
================================================
.DS_Store
*.pyc
*.egg-info
*.swp
tags
ury/docs/current
node_modules/
ury/public/pos
ury/www/pos.html
ury/public/urypos
ury/public/URYMosaic
ury/public/node_modules
urypos/yarn.lock
URYMosaic/yarn.lock
ury/www/urypos.html
ury/www/URYMosaic.html
build
*.lock
================================================
FILE: AGENTS.MD
================================================
# URY — Root Agent Documentation
## 1. App Overview
**URY** is a complete restaurant order management system built as a Frappe/ERPNext custom app.
- **Publisher:** Tridz Technologies Pvt. Ltd
- **Requires:** ERPNext (must be installed in the Frappe bench)
- **Version:** 0.2.1
- **License:** MIT
The system covers the full restaurant workflow: menu management, table management, order taking (POS), kitchen display (KOT), payments (integrated with ERPNext POS), P&L reporting, and multi-branch support.
**POS frontend is located in `/pos` and has its own `AGENTS.MD` with detailed documentation.**
---
## 2. Repository Structure
```
ury/ ← repo root
├── ury/ ← Frappe app Python package (main backend)
├── pos/ ← React 19 POS frontend (v2, current)
├── URYMosaic/ ← Vue 3 KOT kitchen display app
├── urypos/ ← Vue 3 POS frontend (v1, legacy)
├── DEMO/ ← Demo screenshots / assets
├── requirements.txt ← Python dependencies
├── setup.py ← Python package setup
├── package.json ← Yarn workspace root (manages all JS apps)
└── FEATURES.md / README.md ← Human-readable documentation
```
The Yarn workspace (`package.json`) at the root manages three JS sub-projects: `pos/`, `URYMosaic/`, and `urypos/`.
---
## 3. Frappe Backend Structure (`ury/`)
```
ury/
├── hooks.py ← App registration, doc_events, scheduler, fixtures
├── patches.txt ← Migration patch list
├── setup.py ← Custom field creation (add_custom_fields)
├── uninstall.py ← Cleanup on app removal
├── install.py ← Post-install setup
├── permission.py ← check_app_permission for app screen
│
├── ury_pos/
│ └── api.py ← Primary REST API for POS frontend (722 lines)
│ Whitelisted methods: getRestaurantMenu, getBranch,
│ getModeOfPayment, getPosProfile, getAggregatorItem,
│ createPaymentEntry, getInvoiceForCashier, etc.
│
├── ury/
│ ├── doctype/ ← 35 custom Frappe doctypes (see §4)
│ ├── hooks/ ← Document event handlers
│ │ ├── ury_pos_invoice.py ← before_insert, validate, before_submit, on_cancel
│ │ ├── ury_pos_profile.py ← validate
│ │ ├── ury_sales_invoice.py ← before_insert, on_update
│ │ ├── ury_item.py ← validate
│ │ ├── ury_pos_opening_entry.py ← set_cashier_room, before_save
│ │ └── ury_pos_closing_entry.py ← before_save, validate
│ ├── api/ ← Modular API handlers
│ │ ├── ury_kot_display.py ← KOT list, serve/confirm KOT
│ │ ├── ury_kot_order_number.py ← Daily order number logic
│ │ ├── ury_kot_validation.py ← Scheduled KOT validation (every minute)
│ │ ├── ury_kot_notification.py ← Order delay notifications
│ │ └── ury_menu_course_validation.py ← Course priority validation
│ └── page/
│ └── websocket_print/ ← Real-time print via websocket
│
├── www/ ← Web page Python context providers
│ ├── pos.py ← Boot context for /pos page
│ └── *.html ← Build output entry points (generated by Vite build)
│
├── public/ ← Static assets served by Frappe
│ ├── js/ ← Client-side JS injected via hooks.py
│ │ ├── quick_entry.js ← POS quick entry customisation
│ │ ├── pos_print.js ← Standard POS print customisation
│ │ ├── pos_extend.js ← Injected on /point-of-sale page
│ │ ├── restrict_qty_edit_pos.js ← Prevents qty edit in legacy POS
│ │ ├── ury_pos_kot.js ← KOT related JS
│ │ └── sign-message.js / jsrsasign-all-min.js ← QZ Tray signing
│ ├── pos/ ← Built React POS assets (output of `cd pos && yarn build`)
│ ├── URYMosaic/ ← Built KOT display assets
│ └── urypos/ ← Built legacy POS assets
│
├── fixtures/ ← Data fixtures exported via `bench export-fixtures`
│ ├── custom_field.json ← All custom fields definitions
│ ├── role.json ← URY Roles (URY Admin, URY Cashier…)
│ ├── property_setter.json ← Field label overrides
│ ├── custom_html_block.json
│ └── client_script.json
│
├── templates/
│ └── pages/ ← Jinja web page templates (currently minimal)
│
└── patches/
└── v2_0/
└── default_permissions.py ← Post-migration permission setup
```
---
## 4. Key Doctypes
| Doctype | Purpose |
|---|---|
| `URY Order` | Core order document. Created/updated by POS. Linked to POS Invoice on payment. |
| `URY Order Item` | Line items for URY Order. |
| `URY KOT` | Kitchen Order Ticket generated when an order is placed/modified. |
| `URY KOT Items` | Line items for a KOT. |
| `URY Menu` | Menu definition linked to a Restaurant and Price List. |
| `URY Menu Item` | Individual item in a menu with rate and image. |
| `URY Menu Course` | Course grouping (starter, main, dessert) with serve priority. |
| `URY Restaurant` | Restaurant master record. |
| `URY Table` | Table in a restaurant room. |
| `URY Room` | Section/room within a restaurant. |
| `URY Printer Settings` | Thermal printer configuration (IP, port, format). |
| `URY User` | Waiter/cashier assignment to a branch. |
| `Aggregator Settings` | Delivery platform (Zomato, Swiggy) configuration. |
| `Item Add On` | Modifier/add-on for a menu item. |
| `POS Item Variants` | Size/variant options for a menu item. |
| `URY Daily P and L` | Daily P&L report. |
| `URY Cost of Goods` | COGS tracking doctype. |
| `Sub POS Closing` | POS closing record per cashier. |
**Custom fields added to standard ERPNext doctypes** (via `setup.py` + `fixtures/custom_field.json`):
- `POS Invoice` / `Sales Invoice`: `order_type`, `waiter`, `no_of_pax`, `cashier`, `restaurant`, `branch`, `restaurant_table`, `invoice_printed`, `cancel_reason`, `custom_comments`, `custom_ury_order_number`
- `POS Profile`: `restaurant`, `branch`, `printer_settings`, `qz_print`, `qz_host`, `enable_discount`, `enable_multiple_cashier`, `reset_order_number_daily`
- `POS Opening Entry`: `restaurant`, `branch`, `custom_room`, `custom_rooms`
- `Branch`: `user` (URY User table), `custom_aggregators`
- `Customer`: `mobile_number`
- `Price List`: `restaurant_menu`
---
## 5. Document Event Hooks (`hooks.py`)
| DocType | Event | Handler |
|---|---|---|
| `POS Invoice` | `before_insert` | Set arrived_time, validate restaurant/branch |
| `POS Invoice` | `validate` | Validate order fields |
| `POS Invoice` | `after_insert` | Set daily order number |
| `POS Invoice` | `before_submit` | Final validation before submit |
| `POS Invoice` | `on_cancel` / `on_trash` | Cleanup KOTs |
| `POS Profile` | `validate` | Validate printer/restaurant setup |
| `Sales Invoice` | `before_insert`, `on_update` | Copy restaurant fields from linked POS Invoice |
| `Item` | `validate` | Validate menu item configuration |
| `POS Opening Entry` | `validate` | Set cashier room assignment |
| `POS Opening Entry` | `before_save` | Validation before save |
| `POS Opening Entry` | `before_insert` | Set last invoice reference |
| `POS Closing Entry` | `before_save`, `validate` | Closing validation |
**Scheduler:** `ury.ury.api.ury_kot_validation.kotValidationThread` runs every minute to validate KOT state.
---
## 6. Integration Points
### ERPNext Integration
- URY orders ultimately create **POS Invoices** (and **Sales Invoices** on consolidation) in ERPNext.
- Payment is processed via `make_invoice` which calls ERPNext's POS payment flow.
- Price Lists, Customers, Payment Modes, and Tax Templates are all standard ERPNext objects.
### Frontend Apps
- **`/pos`** — React POS v2 (see `pos/AGENTS.MD`)
- **`/urypos`** — Vue POS v1 (legacy, `urypos/`)
- **`/URYMosaic/<production_unit>`** — Vue KOT display (see `URYMosaic/AGENTS.MD`)
All frontends are served as Frappe web pages. Route rules in `hooks.py` (`website_route_rules`) handle SPA routing:
```python
{"from_route": "/pos/<path:app_path>", "to_route": "pos"},
{"from_route": "/URYMosaic/<path:app_path>", "to_route": "URYMosaic"},
```
### Real-time (Socket.io)
- KOT updates are broadcast via Frappe's Socket.io to the `URYMosaic` kitchen display.
- Channel format: `kot_update_{branch}_{production_unit}`
### QZ Tray
- Thermal printing uses QZ Tray (desktop app). The POS sends signed print jobs.
- Signing keys: `public/js/sign-message.js` + `jsrsasign-all-min.js`
- QZ host is configured per POS Profile (`qz_host` field).
---
## 7. Where Frontend Lives
| App | Source | Build output | URL |
|---|---|---|---|
| POS v2 (React) | `pos/src/` | `ury/public/pos/` | `/pos` |
| KOT display (Vue) | `URYMosaic/src/` | `ury/public/URYMosaic/` | `/URYMosaic/<unit>` |
| POS v1 (Vue, legacy) | `urypos/src/` | `ury/public/urypos/` | `/urypos` |
Build commands (from repo root):
```bash
cd pos && yarn build # builds React POS
cd URYMosaic && yarn build # builds KOT display
cd urypos && yarn build # builds legacy POS
```
Or from root: `yarn build` (if configured in root package.json).
After building, run `bench build --app ury` to copy assets to the Frappe public directory.
---
## 8. How Agents Should Navigate This Repo
**To modify POS UI behaviour or translations:**
→ Work in `pos/`. Read `pos/AGENTS.MD` first.
**To modify KOT display logic:**
→ Work in `URYMosaic/`. Read `URYMosaic/AGENTS.MD` first.
**To add or modify a Frappe API endpoint:**
→ Edit `ury/ury_pos/api.py` (main POS API) or `ury/ury/api/<module>.py`.
→ Decorate with `@frappe.whitelist()`. No router config needed.
**To add a new doctype:**
→ Use `bench new-doctype` or create JSON in `ury/ury/doctype/<name>/`.
→ Export fixtures after changes: `bench export-fixtures --app ury`.
**To add a custom field to a standard doctype:**
→ Add via Frappe desk, then export with `bench export-fixtures --app ury`.
→ OR add programmatically in `ury/setup.py` `add_custom_fields()`.
**To add a migration patch:**
→ Create file in `ury/patches/v<major>_<minor>/`.
→ Add path to `ury/patches.txt` under `[post_model_sync]`.
**To change document event behaviour:**
→ Edit the relevant file in `ury/ury/hooks/`.
→ Hook registration is in `hooks.py` `doc_events`.
**Never:**
- Modify `ury/public/pos/`, `ury/public/URYMosaic/`, or `ury/public/urypos/` directly — these are build output, not source.
- Edit `fixtures/custom_field.json` directly — export from Frappe desk instead.
- Add business logic to `www/pos.py` — it should only return boot context data.
================================================
FILE: CLAUDE.MD
================================================
AGENTS.MD
================================================
FILE: FEATURES.md
================================================
# Features of URY App
It's important to note that if no POS Opening entry is created for the day, URY will not allow table selection, ensuring accurate tracking of operations.
A POS Closing Entry must be created at the end of each day to complete the daily operations.
> :information_source: **Note**
> This version is currently designed for **POS machines/Desktop** to handle **cashiers and fast checkout**.
> For **order takers and mobile support**, use **Version 1 POS**, which is available at the path `/urypos/Table`.
- **Key Features**
- All Major POS Features from Version 1 Retained
- Core functionalities from the previous version are preserved for consistency.
- **Unified Order-Taking Interface**
- A single page handles the entire order flow—streamlining operations and reducing clicks.
- **Dynamic Header Search Bar**
- In POS Page: Search for menu items.
- In Order Page: Search by customer name, invoice ID, etc.
- **Table Selection for Dine-In Orders**
- Tables are displayed using shapes and colors.
- Shapes are configurable via the URY Table Doctype.
- **Menu Item Interactions**
- Double Click: Opens detailed product page for customizations.
- Single Click: Instantly adds item to cart.
- **Sidebar Menu Course Navigation**
- A left sidebar on the POS page allows quick switching between menu courses (e.g., Starters, Mains, Desserts).
### Version 1
- **Room Selection**
- To view tables in each room of the restaurant and select their preferred room.
- **Table Selection**
- URY POS table order taking workflow begins with table selection.
- Tables are visually represented as cards, providing flexibility in the selection process
- On the top left side of table have badges that indicated table status:
- Attention(Red): Indicates the table occupied for more than Table Attention time in POS Profile.
- Occupied(Yellow): Indicates occupied tables.
- Free(Green):Signifies available tables
- Active(Blue): Highlights the currently selected table.
- Occupied table propeties
- On the top right of table has button that contains a drop down for table and captain transfer
- Table Transfer : Transfer an order from one table to another after placing the initial order. Clear the original table, and occupy the new table.
- Captain Transfer : Enables the transfer of an order from one captain to another after placing an order at a table.
- `Bill` button to generate a bill against the order, clearing the table.
- `Eye icon` button for navigate to the order page
- Table time is displayed within each card
- On selecting a table with an existing order, will automatically navigate to the menu page.
- **Menu Selection**
- Search bar to quickly locate specific menu items, enhancing speed and accuracy during busy periods.
- Toggle visibility of menu item image based on "View Item Image" checkbox in POS profile.
- Menu filtering with a select box for selecting courses from the restaurant menu , which displays the available courses.
- Menu filtering with Button All - Display all menu item. Priority - displays only prioritized items.
- Menus are presented in a card format which includes the menu name, selected quantity of the item , and +/- buttons for adjusting quantities.
- For precise quantity adjustments, users can click on the quantity display, triggering a dialogue box for easy modification and item wise comments.
- **Customer Selection**
- Option to create a customer using the customer's name and mobile number. You can also add the Customer Group and Territory if needed.
- Option to search for an existing customer using the name or mobile number.
- Option to enter number of pax.
- For returning customers, URY POS displays their top three ordered items in Favourite item section.
- **Cart**
- Displays ordered items and their quantities.
- Users can click on the quantity to open a dialog box for precise adjustments and add item-wise comments.
- Includes a delete button to remove items from the cart.
- Shows the grand total.
- Option to add a general comment to the order.
- Displays additional details such as invoice number, waiter name, POS profile, and cashier.
- Action Buttons in Cart,
- *Update* : Used to generate an order, ie. creating a POS invoice in draft status.
- *Cancel* : To cancel order (draft invoice) and clear the table.
- *KOT Reprint* : Used to reprint KOT.
- **Takeaway Order Taking**
- Takeaway orders can be placed by selecting the `Order Type` on the table page, choosing menu items, adding customer details, and clicking the Update button. This will redirect to the `OrderLog` page.
- Option to search recent orders using invoice ID,customer name and mobile number.
- The Order Log page displays recently placed invoices based on their status:
- `Draft`: Shows takeaway orders and billed table orders.
- `Unbilled`: Displays unbilled table orders.
- `Recently Paid`: Displays a limited number of recently paid invoices, based on the limit set in the POS Profile.
- `Paid`, `Consollidated` and `Return` : Show all paid, consolidated, and returned orders based on the selected order type. These statuses are visible only if the `Allow Cashier to View All Status` checkbox is enabled in the POS Profile.
- `Edit`: Used to modify recent orders.
- `Print Receipt`: To print the invoice.
- `Make Payment`: Allows settling the invoice by selecting a mode of payment.
- `Cancel Order`: To cancel order.
- **Order Printing**
- URY facilitates room-wise printing and offers three distinct methods for executing printing.
- QZ printing
- You may need first install [QZ Tray](https://qz.io/download/) if is not already on your system
- To setup [QZ](https://qz.io/docs/print-server) ,
POS Profile List > POS Profile > QZ Print > QZ Host to enable QZ printing.
- If there are multiple devices for printing , Private IP of the machine hosting the QZ server is given as 'QZ Host'
- Otherwise, use 'localhost' or 127.0.0.1 in the 'QZ Host' data field.
- Network Printing
- Network printing is an alternative option, which functions when QZ printing is in a disabled state.
- To setup it
POS Profile List > POS Profile > Printer Settings
- At the table, tsetting for the printer name is provided with the checkbox 'Bill' set to true.
- The printer name correspond to printer configured in [Network Printer Settings](https://docs.erpnext.com/docs/user/manual/en/print-settings#3-network-printer-print-server) in ERPnext.
- Websocket Printing
- If Either of QZ and Network Printing are not configured , URY will call websocket printing.
- Page can be accessed in `/app/websocket-print` in your browser
**MOSAIC (Kitchen Order Ticket)**
- **KOT Generation:**
- KOT are generated when order is placed in the system
- Update button in order taking and checkout in POS will trigger initial KOT
- **Modified KOT:**
- Adding new item / quantity to the existing order will generate a modified KOT
- **Partially cancelled KOT:**
- Removing an Item / reducing quantity from existing order will generate a Partially cancelled KOT.
- **Cancelled KOT:**
- Created when an entire order is cancelled .
- **KOT Comments**
- Can attach item-wise and order-level comments to KOTs, visible in the Kitchen Display System (KDS)
- **Production Units**
- Production units are used to rule multiple kitchens.
- Each production unit has its own dedicated web-based interface, displaying specific items.
- Printers can be configured separately for each production unit.
- KDS displays are organised by these units , access KDS via
`/URYMosaic/<URY_Production_Unit>`
- **KOT Display**
- KDS make easy to monitor kitchen orders (KOTs) on a screen..
- Receive real-time updates for new KOTs and table changes.
- KOTs are displayed as cards with the following details ,
- Order Type (Table or Takeaway).
- Table name for table orders.
- User who placed the order
- POS Invoice ID as Order ID.
- KOT Created Time
- Item name , quantity and item wise comments.
- Order-level comments.
- Display available quantity and old quantity for canceled orders.
- Ability to mark items as served and unserved.
- Timer against KOT are set in "KOT Warning Time" field within the POS profile to trigger a warning when it's exceeded.
- Can Enable "Notify KOT Delay" for KOT delay notification feature in the POS profile and add recipients roles to the Recipients table.
- Can Enable 'KOT Audio Alert' to play a sound when a KOT is displayed. You can add an audio alert in the 'KOT alert sound' attachment field
- For Cancelled order , card display available quantity and old quantity.
- Clicking outside the items section on a KOT card reveals two buttons:
- "Serve" : Remove Card from the KDS and mark Serve time in KOT document.
- "Confirm" (only for canceled KOTs): Confirms the cancellation..
- Clicking on the card again returns to the original view.
- KOTs are color-coded for easy identification:
- White : New KOT for table orders
- Blue : New KOT for takeaway orders
- Orange : Modified KOT orders
- Red : Cancelled or partially cancelled KOT orders
- **KOT Print**
- Generate physical copies of KOTs
- KOT Prints are generated when order are placed
- Configure printers through network printing in POS profiles for global printing
- To print KOT in the production unit and room, you need to configure the printer separately for each location.
**Daily Profit and Loss:**
- **Daily Profit and Loss report. Here's how it works:**
- Buying Price List :
- Users can set the buying price list to calculate the cost of goods.
- Direct Expenses :
- This section includes expenses such as consumables (burning materials), their cost per unit, and direct fixed expenses (daily fixed expenses). You can set expense names and amounts for items that are part of your daily operational costs.
- Indirect Expenses :
- Indirect expenses consist of electricity costs per unit, expense names, and amounts that are also daily fixed expenses. Additionally, there's a section for percentage expenses, which allows you to store expense percentages based on either net sales or gross sales. You can also set depreciation in this section.
- **Daily P and L Calculation:**
***Once you've set up the necessary data in the Daily Profit and Loss section of URY Report Settings, the system can calculate your daily profit and loss, including the following components:***
- Gross Sales: The total sales for the day..
- Direct Expenses: The sum of consumables and other direct fixed expenses.
- Cost of Goods Sold: This includes the cost of the items sold, factoring in product bundles and Bill of Materials (BOM) sub-items.
- Gross Profit/Loss: The difference between gross sales and the cost of goods sold.
- Employee Cost: The total Employee Cost for the day, including wages, salaries, benefits, and any other related expenses. This cost is part of the Total Indirect Expense.
- Indirect Expenses: The total of indirect fixed expenses and any percentage-based expenses.
- Net Profit/Loss: The final profit or loss for the day.
***By inputting daily readings for consumables, electricity, and any other expenses, and then saving and submitting the document, you can generate a detailed daily profit and loss report, complete with a breakdown of the cost of goods sold. This report is an invaluable tool for restaurant owners and managers to track their financial performance on a daily basis.***
- **Reports:**
***It offers a wide range of reports, including the following:***
1. Today's Sales
2. Daywise Sales
3. Daywise Invoices
4. Month Wise Sales
5. Average Bill Value
6. Cancelled Invoices
7. Item Wise Sales
8. Customer Data
9. Repeated Customers
10. Daywise Customer Details
11. Employee Sales
12. Employee Item Wise Sales
13. Service Wise Sales
14. Time Wise Sales
- **Customizable Branch Timings:**
- URY allows you to set varying branch timings in the URY Report settings, including extended hours. This feature ensures that your reports accurately reflect the operational hours of your restaurant.
================================================
FILE: INSTALLATION.md
================================================
# URY Installation
While URY may work on existing ERPNext instance, it is recommended that you setup URY on a new frappe site created for URY.
<div align="center">
<a href="https://frappecloud.com/dashboard/signup?product=ury" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/try-on-fc-white.png">
<img src="https://frappe.io/files/try-on-fc-black.png" alt="Try on Frappe Cloud" height="28" />
</picture>
</a>
</div>
> :information_source: Note :
> Minimum Node Version 18.20.*+ required
- Install ERPNext using the [official installation guide](https://github.com/frappe/bench#installation).
**To Install ERPNext to your bench:**
```sh
bench get-app --branch version-15 erpnext https://github.com/frappe/erpnext.git
```
**To Install Frappe HR to your bench:**
Frappe HR is a dependency for employee management reports in URY
```sh
bench get-app --branch hrms https://github.com/frappe/hrms.git
```
**Install the URY app to your bench:**
```sh
bench get-app ury https://github.com/ury-erp/ury.git
```
**Create New site :**
```sh
bench new-site sitename
```
**Install ERPNext to the site :**
```sh
bench --site sitename install-app erpnext
```
**Install Frappe HR to the site :**
```sh
bench --site sitename install-app hrms
```
**Install URY app to the site :**
```sh
bench --site sitename install-app ury
```
**Build the site :**
```sh
bench --site sitename build
```
**Migrate the site :**
```sh
bench --site sitename migrate
```
================================================
FILE: LICENSE
================================================
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
================================================
FILE: MANIFEST.in
================================================
include MANIFEST.in
include requirements.txt
include *.json
include *.md
include *.py
include *.txt
recursive-include ury *.css
recursive-include ury *.csv
recursive-include ury *.html
recursive-include ury *.ico
recursive-include ury *.js
recursive-include ury *.json
recursive-include ury *.md
recursive-include ury *.png
recursive-include ury *.py
recursive-include ury *.svg
recursive-include ury *.txt
recursive-exclude ury *.pyc
================================================
FILE: README.md
================================================
# URY - Open Source Restaurant Management System
URY is an open source ERP designed to simplify and streamline restaurant operations. It is built on top of world's best free and open source ERP, ERPNext.
<div align="center">
<a href="https://frappecloud.com/dashboard/signup?product=ury" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/try-on-fc-white.png">
<img src="https://frappe.io/files/try-on-fc-black.png" alt="Try on Frappe Cloud" height="28" />
</picture>
</a>
</div>
> :warning: Warning :
> URY is currently in active development, and we are continuously making changes, updates, and working on new features and improvements. Please be aware that until a stable release is reached, backward compatibility is not guaranteed. We make every effort to maintain compatibility.
> :information_source: Note :
> Our system has been successfully running at scale, serving over 10+ outlets for the past 10 months.
## What It Includes
- **POS**: Dine‑in, takeaway, delivery, offline mode, printer management
- **Kitchen Display**: Real‑time order queues, KOT printing
- **Analytics**: P&L dashboard, consumption reports, item trends
Given below is the list of features of URY app.
### URY POS
**URY POS** is a light weight and easy to use web-based application designed for streamlined order management. It serves as an efficient tool for both cashiers and captains, facilitating order processing at the cash counter and tables.It supports various order types, including dine-in, delivery, takeout and Aggregator. URY POS is compatibile with a wide range of devices, including desktops, tablets, and smartphones.
:information_source: **Note:**
> To access the previous version of the separate URY POS app, [click here](https://github.com/ury-erp/pos).
> **Use the URY branch `v1` to access these separate apps.**
> **Support for this version will end in December 2025.**
### URY MOSAIC
**URY MOSAIC** is an interactive Kitchen Display System (KDS) designed to simplify order management in both single and multi-kitchen restaurants. Additionally, it offers optional Kitchen Order Ticket (KOT) printing support for added convenience.
:information_source: **Note:**
> To access the previous version of the separate URY MOSAIC app, [click here](https://github.com/ury-erp/mosaic).
> **Use the URY branch `v1` to access these separate apps.**
> **Support for this version will end in December 2025.**
### Daily P & L and Reports
URY has daily P & L and various reports. It helps restaurants to monitor daily Profit and Loss (P&L), utility consumption, disposables usage, and other key metrics with precision and ease. It provides restaurants with crucial data, enabling timely decision-making by presenting essential information and insights.
:information_source: **Note:**
> To access the previous version of the separate URY PULSE app, [click here](https://github.com/ury-erp/pulse).
> **Use the URY branch `v1` to access these separate apps.**
> **Support for this version will end in December 2025.**
## Features
### POS & Billing
* Role-based access with strict operational controls
* Pre-billing checklists to enforce compliance (e.g., stock check, hygiene checklist)
* Linked with stock and accounting modules
* Multi-format support: Table service, QSR, and takeaway
* Multi-cashier handling and terminal controls
* Advanced filters for order and bill management
* Modern, fast UI with guided flow
* Shift opening, closing, and cash reconciliation built-in
### Menu & Recipe Management
* Centralized menu with outlet-level control
* Recipe mapping using Bill of Materials (BOM)
* Control pricing, availability, and portions per outlet
* Supports combos, modifiers, and item bundles
* Integrated with production planning for daily prep
### Table Order Management
* Mobile-first order taking for waitstaff
* Live sync with kitchen and cashier
* Real-time inventory checks before order placement
* Supports modifiers, course sequencing, and notes
* Seamless integration with billing and KDS
### Kitchen Display & KOT Management
* Supports multiple kitchens with advanced printer routing
* Interactive KDS with live status updates (Preparing, Ready, Served)
* Delay, cancellation, and modification tracking
* Real-time kitchen analytics
* Seamless flow from order to service across stations
### Operational Red Flags & Alerts
* Delayed orders and preparation time breaches
* KOT not started after order placement
* Unclosed bills and prolonged table occupancy
* Excessive KOT cancellations and modifications
* Real-time alerts for operational exceptions
* Dashboard view for quick issue resolution across outlets
### Reports & Analytics
* Daily Profit & Loss
* Shortage and Excess reporting
* Course-wise and item-wise performance
* Captain and staff performance tracking
* Branch-wise and outlet-wise comparisons
* Customer-wise sales trends
* Detailed sales, production, and stock reports
* Real-time operational insights for better decision-making
For more comprehensive list of features [go here.](FEATURES.md)
## Getting Started
To start using URY, you need to first install URY and then setup your first restaurant.
1. [URY Installation Guide](INSTALLATION.md).
2. [URY Setup Instructions](SETUP.md).
## Looking for other versions
1. Use branch `v1` to use ury [v0.1.0]
## About
URY is developed by [Tridz Technologies Pvt Ltd](https://tridz.com) and supported by [Frappe](http://frappe.io).
## Terms and Conditions
By using the URY, you agree to use it responsibly and in compliance with applicable laws. URY is built on open-source technology and is provided for your convenience to manage restaurant operations. While we strive to keep the app reliable, it is provided “as is” without any guarantees, and we are not responsible for any misuse or resulting issues.
[Read More](TERMS.md)
================================================
FILE: SETUP.md
================================================
## URY Setup
This guide takes you step-by-step through setting up URY on top of ERPNext
### Step 1 : Company
- Login into the site and Follow the installation wizard
- Specify the language.
- Provide country , timezone and currency details.
- Create the first user.
- Enter company name, its description, and select a bank account.
- click on 'Complete setup'
### Step 2 : Users and Roles
- To manage restaurant operations in URY, you’ll need to set up specific user roles in the ERPNext/Frappe system. Use the [Frappe/ERPNext interface](https://docs.erpnext.com/docs/user/manual/en/adding-users) to create a new user.
- Assign one of the three URY roles to users:
- URY Manager - Responsible for overseeing and managing all restaurant operations.
- URY Captain - Responsible for managing customer orders and table service.
- URY Cashier - Responsible for managing customer orders, table service, and handling payments and POS operations.
- Below are the recommended DocType permissions for URY roles. These permissions cover only the basic restaurant operations needed for URY (such as table service, billing, and order handling). You can extend or modify them later based on your restaurant’s workflow and access needs.
<details>
<summary>View Role Permissions</summary>
| Role | Doctype | Perm | Select | Read | Write | Create | Delete | Submit | Cancel | Amend | Print | Email | Report | Import | Export | Share |
| :---- | :---- | :---- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| **URY Captian** | Company | 0 | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - | - | - |
| | Currency | 0 | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - | - | - |
| | Customer | 0 | ✓ | ✓ | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - |
| | Item Price | 0 | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - | - | - |
| | POS Invoice | 0 | ✓ | ✓ | ✓ | ✓ | - | - | - | - | ✓ | - | - | - | - | - |
| | POS Opening | 0 | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - | - | - |
| | URY KOT | 0 | ✓ | ✓ | ✓ | ✓ | - | ✓ | ✓ | - | - | - | - | - | - | - |
| | URY KOT Error Log | 0 | ✓ | ✓ | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - |
| **URY Cashier** | Company | 0 | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - | - | - |
| | Currency | 0 | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - | - | - |
| | Customer | 0 | ✓ | ✓ | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - |
| | Item Price | 0 | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - | - | - |
| | POS Profile | 0 | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - | - | - |
| | POS Invoice | 0 | ✓ | ✓ | ✓ | ✓ | - | ✓ | ✓ | - | - | - | - | - | - | - |
| | POS Opening | 0 | ✓ | ✓ | ✓ | ✓ | - | ✓ | - | - | - | - | - | - | - | - |
| | POS Closing | 0 | ✓ | ✓ | ✓ | ✓ | - | ✓ | - | - | - | - | - | - | - | - |
| | Sales Invoice | 0 | ✓ | ✓ | ✓ | ✓ | - | ✓ | - | - | - | - | - | - | - | - |
| | User | 0 | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - | - | - |
| | User | 1 | - | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - | - |
| | URY KOT | 0 | ✓ | ✓ | ✓ | ✓ | - | ✓ | ✓ | - | - | - | - | - | - | - |
| | URY KOT Error Log | 0 | ✓ | ✓ | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - |
</details>
### Step 3 : Branch
- Create [Branch](https://frappehr.com/docs/v14/en/branch) in ERPNext.
Branch setup manage users, POS access, and aggregator configurations for delivery platforms, ensuring smooth operations.
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/refs/heads/develop/DEMO/Branch.png" style="margin: 2rem 2rem;" alt="Branch">
</div>
- Specify branch users in the table; note that only these users can access the POS of that branch.
- **Aggregator Settings** : If your restaurant uses aggregator platforms (such as food delivery services), configure the following details:
- Customer : Create or link the customer profile for aggregator orders.
- Price List : Create a Selling type price list for the aggregator and ensure that item prices are added to this list so they appear correctly in the menu.
- Mode of Payment : Set up a mode of payment for aggregator transactions.
- **Keep Sales Invoice Unpaid** : Check this box if you want the aggregator's sales invoice to remain unpaid.
- **Create Invoice without Tax** : Check this box if you want the aggregator's invoice to be created without tax. This applies to both Sales Invoice and POS Invoice.
### Step 4 : URY Restaurant
- Go to the "URY Restaurant List" and create a new restaurant with the following details:
The restaurant setup links your company, branch, and menu together, defining details like tax templates, invoice series, and menu configurations for rooms and order types.
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/develop/DEMO/URY%20Restaurant.png" style="margin: 2rem 2rem;" alt="URY Restaurant">
</div>
- **Name** : Restaurant name
- **Company**: Specify the company under which the restaurant is being created.
- **Invoice Series Prefix**: allows you to define prefix for naming of an Invoice .
- **Aggregator Series Prefix**: allows you to define prefix for naming of a aggregator Invoice.
- **Branch** : Select the branch associated with the restaurant .
- **Default Tax Template** : Mention the [Sales tax](https://docs.erpnext.com/docs/user/manual/en/sales-taxes-and-charges-template) value if applicable.
- **Address** : Provide the address of the restaurant.
- **Default Menu** : Select Menu against the restaurant.
- **Room Wise Menu** : To enable room wise menu.
- **Menu For Room** : Add restaurant menu against each room to handle room wise price list.
- **Order Type Wise Menu** : To enable order type wise menu for cashier.
- **Menu For Order Type** : Add restaurant menu against each order type to handle order type wise price list.
### Step 5 : URY Room
- Next is to Create Restaurant Room with the following details :
Rooms help organize your restaurant layout—such as indoor, outdoor, or VIP areas and define where orders and print actions (like bills or KOTs) are directed. Each room can have its own printer setup to manage room-wise printing. Make sure to add the room to the corresponding URY Restaurant.
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/refs/heads/develop/DEMO/URY%20Room.png" style="margin: 2rem 2rem;" alt="URY Room">
</div>
- **Name** : Specify a unique name to the room.
- **Room Type** : Select the type from the list.
- **Print Settings** : Choose a network printer.
- **Bill** : Enable for Invoice Printing .
- **KOT Print** : Enable for KOT Printing .
### Step 6 : Item
- Create [Item](https://docs.erpnext.com/docs/user/manual/en/item) to be included in the URY Menu.
- If an item is sold in a bundle, consider using the [Product Bundle](https://docs.erpnext.com/docs/user/manual/en/product-bundle) feature.
### Step 7 : URY Menu
- Create Restaurant Menu From "URY Menu List" with the following details:
Menu define the list of items available for order, their prices, and how they’re displayed in the POS.
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/develop/DEMO/URY%20Menu.png" style="margin: 2rem 2rem;" alt="URY Menu">
</div>
- **Name** : Specify a unique name to the menu .
- **Restaurant** : Linked to URY Restaurant to select restaurant .
- **Branch** : This field will be automatically populated when you select a restaurant.
- **Enabled** : Activate the checkbox to enable the menu.
- **Items** : List the items included in the menu and their respective rates.
- **Special Dish** : You can use this checkbox in the Item table to show an item as a `Special Items` or `Priority` item for menu display.
- **Disabled** : You can use this checkbox to remove item from menu as per need.
- **Course** : Categorize each item based on the course type (e.g., Starters, Mains, Desserts). In POS, the menu can be categorized and viewed by Course. If the **Indicate in KDS** checkbox is enabled for a course, the Kitchen Display System (KDS) will use the serving priority to determine the preparation and serving order of items.
Example:
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/develop/DEMO/URY%20Menu%20Course.png" style="margin: 2rem 2rem;" alt="URY Menu Course">
</div>
### Step 8 : URY Table
- Create tables for the restaurant in the "URY Table List" with the following details:
Tables define the seating layout, their availability and are visually displayed on the POS for easy selection.
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/refs/heads/develop/DEMO/URY%20Table.png" style="margin: 2rem 2rem;" alt="URY Table">
</div>
- **Name** : Specify the table name that will be listed in URY Order.
- **Restaurant** : Select the associated restaurant.
- **Restaurant Room** : Specify a room to which the table belong ( if no room in restaurant , create a default room and select it for all)
- **Branch** : This field will be auto-populated when the restaurant is selected.
- **No of seat** : Input the number of seats at the table.
- **Minimum seating** : Specify minimum seating capacity .
- **Is Take Away** : For take away orders ( Order type will be "Take Away") .
- **Active info** : Record table status and time . Both are read-only .
- **Table Shape** : Use this option to add the table shape for display on the POS screen.
### Step 9 : POS Profile
- [Create POS Profile](https://docs.erpnext.com/docs/user/manual/en/pos-profile) in ERPNext with the following additional fields:
- **Printer Info**
- Configure printing options for invoices and KOTs. For network printers, select the printer in the printer settings table. For QZ printing, enable QZ Print and enter the host IP.
<p>
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/develop/DEMO/POS%20Profile%20Network%20Printer.png" style="margin: 2rem 2rem;" alt="POS Profile QZ">
</div>
</p>
- **Printer Settings** : Select a printer, enable Bill for invoice printing and KOT Print for KOT printing. For multiple rooms with separate printers, configure the printer settings in the corresponding URY Room for room-wise printing.
<p>
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/develop/DEMO/POS%20Profile%20QZ.png" style="margin: 2rem 2rem;" alt="POS Profile QZ">
</div>
</p>
- **QZ Print** : Check this box to enable QZ printing.
- **QZ Host** : Enter the Network IP for QZ printing.
- **URY POS Restrictions**
- Set role-based permissions, cashier access, table order restrictions, and operational settings such as attention time, discount, daily POS closing, and visibility of paid invoices.
<p>
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/refs/heads/develop/DEMO/POS%20Profile%20Restrictions.png" style="margin: 2rem 2rem;" alt="POS Profile Restrictions">
</div>
</p>
- **Captain Transfer Role Permissions** : Roles added to this field will have permission for 'Captain Transfer'. Users with this role will also have access to all tables.
- **Role Allowed For Billing** : Users assigned this role will function as cashiers in URY POS, responsible for managing billing transactions.
- **Role Restricted For Table Order** : Users with this role have restricted access to table order functions.
- **Table Attention Time** : To indicate "Attention" status in the table of URY POS.
- **Show Limited Paid Invoices** : Set a limit for the cashier to view a restricted number of recently paid invoices.
- **Allow Cashier To View All Status** : Enables Cashiers to view all statuses (Paid, Consolidated, Return Invoices) in the recent order.
- **Allow Cashier To Edit And Remove Table Order Items** : To permit cashier to edit and remove table orders.
- **Show Item Image In URY POS** : To show image in URY POS.
- **Require Daily POS Closing** : Validate that the previous day’s POS is closed before opening a new one.
- **Enable Discount** : Enable discount feature in URY POS.
- **Enable Order Type Edit** : Use this option to change the order type of an existing invoice.
- **Multiple Cashier Configuration**
- **Enable Multiple Cashier** : Enable this checkbox if the outlet has multiple cashiers. Then, add the cashiers under 'Applicable for Users' and enable the 'Main Cashier' checkbox for the head cashier.
- **KOT Settings**
- Configure how Kitchen Order Tickets (KOTs) are managed and monitored, including naming series, timers, reprint and audio alert options, delay notifications with recipients, and daily order number resets to streamline kitchen operations.
<p>
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/refs/heads/develop/DEMO/POS%20Profile%20KOT%20Settings.png" style="margin: 2rem 2rem;" alt="POS Profile KOT Settings">
</div>
</p>
- **URY KOT Naming Series** : Add a naming series for KOT. A KOT will be created only if a naming series is set.
- **KOT Warning Time** : Timer against KOT are set in this field to trigger a warning when it's exceeded in KDS.
- **Enable KOT Reprint** : Use this option if you need the reprint feature for KOT prints. Make sure to add the appropriate printers and print format.
- **Enable KOT Audio Alert** : Can enable to play a sound when a KOT is displayed. You can add an audio alert in the `KOT alert sound` attachment field.
- **Notify KOT Delay** : Can enable for KOT delay notification and add recipient roles to the Recipients table.
- **Recipients (By Role)** : Add roles of users to receive KOT delay notifications.
- **Reset Order Number Daily** : Use this option to reset the order number for POS Invoices on a daily basis. Note that this is not the invoice number.
> **Note:** Update the Price List in Accounting to restaurant menu price list.
### Step 10: URY Production Unit
- **URY Production Unit**
Create Production Unit From "URY Production Unit" with the following details:
Production Units manage multiple kitchens, each with its own web-based interface for displaying KOTs in the Kitchen Display System (KDS). Printers can also be configured per unit for physical KOTs if needed.
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/refs/heads/develop/DEMO/URY%20Production%20Unit.png" style="margin: 2rem 2rem;" alt="URY Production Unit">
</div>
- **Production** : Enter the name for your Production Unit.
- **POS Profile** : Select the POS Profile
- **Branch** : Auto fetch when pos profile is selected
- **Warehouse** :Auto fetch when pos profile is selected.
- **Item Groups** :Select Item Groups that belong to the production unit.
- **Printers** : Table to configure printing inside production unit.
- **Printer** : Select Network printer.
- **KOT Print** : Enable for KOT Printing .
- **KOT Print Format** : Select print format for KOT .
- **Block Takeaway KOT** : Enable for block Takeaway KOT printing .
> **Note:** To access KDS follow the site url with `/URYMosaic/Production%20Unit%20Name`. eg: [https://ury.xxxx.com/URYMosaic/Kitchen](https://ury.xxx.com/URYMosaic/Kitchen)
### Step 11 : User Permissions
- User Permissions control access to specific records in ERPNext. Give [User Permission](https://docs.erpnext.com/docs/user/manual/en/user-permissions) to the user for the documents they need to access, such as:
- POS Profile
- Branch
### Step 12 : Printer Setup
- QZ Printer
- Add your certificate file is at `ury/public/files/cert.pem`.
- Update the `pos/privateKey.js` for v2 and `urypos/privateKey.js` for v1.
- Network Printer
- Set up CUPS (Common Unix Printing System) for network printing.
- In Network Printer Settings, add your printers and note their and enter the corresponding printer in the URY Room for invoice printing from POS.
<p>
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/refs/heads/develop/DEMO/Network%20Printer%20Settings.png" style="margin: 2rem 2rem;" alt="Network Printer Settings">
</div>
</p>
### Step 13 : Customer Search
- frappe.utils.global_search is used for customer searching ,you have to run the following commands for building search index
```
bench --site site-name build-search-index
```
and
```
bench --site site-name rebuild-global-search
```
### Step 14: Multiple Cashier Configuration
- Follow the steps below to set up and manage multiple cashier operations in URY, allowing multiple cashiers to handle billing under one POS profile with individual transaction control.
- **Create Cashier User** : Create user with the URY Cashier role.
- **Assign Rooms** : Assign URY Rooms to users. Users can only access POS for the rooms they are assigned to.
- **Configure POS Profile** :
- In the Applicable for Users table of the POS Profile, add all cashiers for the POS and enable Main Cashier for the head cashier.
- Enable Multiple Cashier in the Multiple Cashier Configuration.
- **Workflow**
- POS Opening
- The main cashier creates the POS Opening Entry first, followed by sub cashier. The main cashier must always open the POS first.
- Order Processing
- Proceed with normal order taking and restaurant operations.
- Sub POS Closing
<div style="text-align: center;">
<img src="https://raw.githubusercontent.com/ury-erp/ury/refs/heads/develop/DEMO/Sub%20POS%20Closing.png" style="margin: 2rem 2rem;" alt="Sub POS Closing">
</div>
- Sub cashier creates a Sub POS Closing Entry.
- Sub POS Closing Entry is an URY Doctype to reconcile sub cashier transactions separately.
- POS Closing
- Main cashier creates a POS Closing Entry
### Step 15: URY Report Setting
- Navigate to **URY Report Settings** in your site.
- Click on **Add URY Report Settings**.
- Under the **Details** tab:
- **Extended Hours** : Enable this if the branch operates after 12 AM.
- **No of Hours** : Enter the number of hours, if extended hours is enabled.
- Under the **Daily P and L Settings** tab:
- **Buying price List** : Select the buying price list for your branch.
- Under **Direct Expenses**:
- **Burning Materials (Other Consumables)** : Table to list consumables.
- **Material** : Enter the Material (e.g., gas, charcoal).
- **Cost Per Unit** : Specify the cost per unit for each material.
- **Direct Fixed Expenses** : Table to add list of daily fixed direct expenses.
- **Expense** : Provide the expense name.
- **Amount** : Specify amount for each expense.
- Under **Indirect Expenses**:
- **Electricity Charges**: Enter the electricity charges per unit.
- **Indirect Fixed Expenses** : Table to list daily fixed indirect expenses.
- **Expense** : Provide the expense name.
- **Amount** : Specify amount for each expense.
- **Percentage Expenses** : Table to list of expenses as a percentage of sales.
- **Expense** : Provide the expense name.
- **Percentage Type** : Choose the percentage type (Net Sales or Gross Sales).
- **Percent** : Specify the percentage of the selected type.
- Under **Employee Costs**:
- **Employee Costs** : Table to list daily fixed expenses as a part of employee costs.
- **Expense** : Provide the expense name.
- **Amount** : Specify amount for each expense.
- **Depreciation** : Add depreciation amount if applicable.
- **Daily Gross Salary Cost is calculated from employees attendance.**
- Follow these steps to set up the payment type and payment amount for employees:
#### Step 1:
- Navigate to **Employee** in your site.
- Choose the relevant **Employee**.
#### Step 2:
- Under the **Salary** tab:
- **Payment Type** : Choose between Salary or Daily Wage.
- **Payment Amount** : Enter the corresponding payment amount.
Follow the [Attendance documentation](https://frappehr.com/docs/v14/en/attendance#3-features) for marking the attendance or use the [Employee Attendance Tool](https://frappehr.com/docs/v14/en/employee-attendance-tool#2-how-to-mark-attendance-using-employee-attendance-tool)
================================================
FILE: TERMS.md
================================================
# Terms and Conditions – URY
By using **URY** (“the App”), you agree to the following:
1. **Usage**
* The App is provided to help manage restaurant operations, including order management and POS.
* You are solely responsible for how you use the App. We are **not liable for any misuse, data loss, or damages** arising from your usage.
2. **Open Source**
* URY is built on **Frappe/ERPNext**, which are open-source frameworks.
* Your use of the App and its open-source components is subject to their respective licenses.
3. **No Warranty**
* The App is provided **“as is” without any warranties** of performance, reliability, or fitness for a particular purpose.
4. **Limitation of Liability**
* We are **not responsible** for any direct, indirect, or consequential damages resulting from the use or inability to use the App.
5. **Termination**
* You may stop using the App at any time.
================================================
FILE: URYMosaic/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: URYMosaic/.vscode/extensions.json
================================================
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}
================================================
FILE: URYMosaic/AGENTS.MD
================================================
# URYMosaic — Agent Documentation
## 1. Overview
**URYMosaic** is a Vue 3 real-time Kitchen Order Ticket (KOT) display system. It runs on a screen in the kitchen and shows live order tickets as they are placed or modified by the POS. Kitchen staff use it to track what to prepare and mark orders as served.
- **URL:** `/URYMosaic/<production_unit_name>` (e.g., `/URYMosaic/Kitchen`)
- **Served as:** Frappe web page (`ury/www/URYMosaic.html`)
- **Build output:** `ury/public/URYMosaic/`
The production unit name in the URL determines which KOTs are displayed. Each physical kitchen station has its own URL pointing to its production unit.
---
## 2. Tech Stack
| Concern | Library / Version |
|---|---|
| UI framework | Vue 3.3.4 (Options API) |
| Build tool | Vite 4.4.5 |
| Real-time | socket.io-client 4.5.1 |
| Layout | masonry-layout 4.2.2 |
| Styling | Tailwind CSS 3.3.3 |
| Backend API | frappe-js-sdk 1.3.6 |
| Routing | vue-router 4 |
---
## 3. Project Structure
```
URYMosaic/
├── src/
│ ├── main.js ← Vue app entry point
│ ├── App.vue ← Root component: mounts Header + KOT
│ │
│ ├── components/
│ │ ├── kot.vue ← Main KOT display (entire app logic lives here)
│ │ └── Header.vue ← Top bar with logo and refresh button
│ │
│ ├── views/
│ │ ├── Home.vue ← Unused placeholder
│ │ └── Login.vue ← Simple login form (Frappe session auth)
│ │
│ ├── router/
│ │ ├── index.js ← Routes: / → KOT component
│ │ └── auth.js ← Login route + auth guard
│ │
│ └── assets/
│ └── logos/ ← mosaic.jpg logo
│
├── public/ ← Static assets
├── index.html ← HTML entry
├── vite.config.js
└── package.json
```
---
## 4. Key Components
### `kot.vue` — The Core Component
This is where all business logic lives. It is a large Vue Options API component (~620 lines).
**Data properties:**
| Property | Purpose |
|---|---|
| `kot` | Array of KOT objects fetched from API |
| `production` | Production unit name (extracted from URL path) |
| `branch` | Branch code from API response |
| `kot_channel` | Socket.io channel name: `kot_update_{branch}_{production}` |
| `kot_alert_time` | Minutes threshold for alerting delayed orders |
| `audio_alert` | Whether to play sound on new KOT |
| `daily_order_number` | Whether to show daily order number (vs invoice suffix) |
| `isOnline` | Network status (drives status message display) |
**Key methods:**
| Method | Purpose |
|---|---|
| `auth()` | Gets logged-in Frappe user; shows modal if unauthenticated |
| `fetchKOT()` | `call.get('ury.ury.api.ury_kot_display.kot_list')` — loads all active KOTs |
| `serveOrder(kot)` | `call.post(serve_kot)` — marks KOT as served, hides card |
| `confirmOrder(kot)` | `call.post(confirm_cancel_kot)` — acknowledges a cancelled KOT |
| `rotateCard(kot)` | Toggles overlay on KOT card (reveals Serve/Confirm button) |
| `toggleItemStrikeThrough(kotitem, kot)` | Marks individual items as done (stored in localStorage) |
| `updateColorandTable(kot, …)` | Sets card background colour based on order type/status |
| `updateTimeRemaining()` | Recalculates elapsed time for all KOTs |
| `orderDelayNotify(kot)` | Sends delay notification when KOT reaches alert threshold |
| `masonryLoading()` | Re-runs Masonry layout after DOM updates |
| `handleOnline/Offline()` | Network status handlers — refreshes KOTs on reconnect |
**KOT card colours:**
- White → Dine-in
- `bg-blue-100` → Takeaway
- `bg-[#FFD493]` → Order Modified
- `bg-[#FFD2D2]` → Cancelled / Partially cancelled
### `Header.vue`
Minimal header with logo and a refresh button that calls `window.location.reload()`.
---
## 5. API Interactions
All API calls use a `FrappeApp` instance initialised with the current page URL:
```javascript
const frappe = new FrappeApp(url); // url = window.location.origin
const call = frappe.call();
```
**Endpoints called:**
| Endpoint | Method | Purpose |
|---|---|---|
| `ury.ury.api.ury_kot_display.get_site_name` | GET | Gets Frappe site name for Socket.io namespace |
| `ury.ury.api.ury_kot_display.kot_list` | GET | Returns all active KOTs + branch config |
| `ury.ury.api.ury_kot_display.serve_kot` | POST | Mark a KOT as served |
| `ury.ury.api.ury_kot_display.confirm_cancel_kot` | POST | Acknowledge cancelled KOT |
| `ury.ury.api.ury_kot_notification.order_delay_notification` | POST | Trigger delay notification |
**`kot_list` response structure:**
```json
{
"message": {
"Branch": "Main Branch",
"KOT": [...],
"kot_alert_time": 15,
"audio_alert": 1,
"daily_order_number": 1
}
}
```
Each KOT object contains: `name`, `time`, `type`, `restaurant_table`, `table_takeaway`, `is_aggregator`, `customer_name`, `aggregator_id`, `order_no`, `invoice`, `comments`, `production`, `kot_items[]`.
---
## 6. Data Flow
```
1. mounted() called
│
├── fetchAndSetSiteName() → window.globalSiteName
├── initializeSocket() → connects socket.io to site namespace
├── auth() → verifies Frappe session
└── fetchKOT()
│
├── Sets: branch, kot_alert_time, audio_alert, daily_order_number, kot_channel
├── this.kot = result.message.KOT
└── updateQtyColorTable() → updateTimeRemaining() → masonryLoading()
2. Socket.io listener: socket.on(kot_channel, doc)
│
├── Play audio if enabled
├── this.kot.unshift(doc.kot) ← prepend new KOT
├── masonryLoading()
├── updateQtyColorTable()
└── updateTimeRemaining()
3. setInterval(updateTimeRemaining, 60000) ← every 60 seconds
```
---
## 7. Extension Points
**Adding a new KOT type / colour:**
- `updateColorandTable()` in `kot.vue` — add a new `else if` condition.
**Adding a new action button:**
- Add button inside the `.isRotated` overlay div in the KOT card template.
- Wire to a new method that calls the appropriate Frappe API.
**Adding new KOT data fields to the display:**
- The `kot_list` API (`ury/ury/api/ury_kot_display.py`) returns the raw KOT fields.
- Add the field to the KOT doctype if needed, return it from `kot_list`, then render in the template.
**Changing the production unit filter:**
- The URL path segment becomes `this.production` via `decodeURIComponent(parts[parts.length - 1])`.
- KOTs are filtered server-side by `kot.production === this.production` on the client.
- To filter differently, modify `kot_list` in `ury_kot_display.py`.
**Changing layout:**
- Masonry grid: `.grid` div with `.masonry-item` children. Adjust `cols-` Tailwind classes on the grid.
- Masonry is re-initialised on every data change via `masonryLoading()` — always call this after updating `this.kot`.
---
## 8. Rules for Modifying Safely
### Socket channel naming
The channel name is `kot_update_{branch}_{production}`. This string must match what the backend publishes in `ury_kot_display.py` when broadcasting KOT updates. Do not change either side independently.
### Production unit in URL
The `production` value comes from the URL path. This is case-sensitive and must match the `URY Production Unit` doctype name exactly. Changing URL structure requires updating `website_route_rules` in `ury/hooks.py`.
### KOT `showDiv` flag
KOTs are hidden from the board by setting `kot.showDiv = true` (not by removing from array). This keeps array indices stable during socket updates. Do not filter the `kot` array reactively.
### LocalStorage strike-through state
`toggleItemStrikeThrough` persists `{kotName}_{itemName}_strike` keys in localStorage. `removeAllItemsFromLocalStorage` cleans up after a KOT is served/confirmed. Ensure any KOT removal also calls this cleanup.
### Audio alerts
`audio_alert` is fetched from the backend (`POS Profile` / `URY Printer Settings`). Audio only plays if `audio_alert === 1` AND the user has clicked the page at least once (browser autoplay policy). The `showAudioAlertMessage` banner guides users to click.
### Authentication
If `auth()` fails, `this.showModal = true` shows a "Not Permitted / Login" modal. The redirect goes to `/login?redirect-to=URYMosaic/{production}`. Do not remove this auth check.
### What NOT to change
- The `kot_channel` computation — changing it breaks real-time updates.
- The `frappe.call()` initialisation pattern — `url` must be the site origin, not a path.
- `masonryLoading()` call pattern — always call it via `this.$nextTick()` inside to avoid layout before DOM updates.
- The `kot.isRotated` card flip pattern — it is the only user interaction to reveal action buttons; modifying it affects kitchen UX.
================================================
FILE: URYMosaic/CLAUDE.MD
================================================
AGENTS.MD
================================================
FILE: URYMosaic/README.md
================================================
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
================================================
FILE: URYMosaic/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/ury.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>URY Mosaic</title>
</head>
<body>
<div id="app"></div>
<script>window.csrf_token = '{{ frappe.session.csrf_token }}';</script>
<script type="module" src="/src/main.js"></script>
</body>
</html>
================================================
FILE: URYMosaic/package.json
================================================
{
"name": "urymosaic",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --base=/assets/ury/URYMosaic/ && yarn copy-html-entry",
"preview": "vite preview",
"copy-html-entry": "cp ../ury/public/URYMosaic/index.html ../ury/www/URYMosaic.html"
},
"dependencies": {
"socket.io-client": "^4.5.1",
"vue": "^3.3.4",
"vue-router": "^4",
"autoprefixer": "^10.4.15",
"axios": "^1.5.0",
"flowbite": "^1.8.1",
"flowbite-vue": "0.0.17-next.6",
"frappe-js-sdk": "^1.3.6",
"masonry-layout": "^4.2.2",
"postcss": "^8.4.30",
"tailwindcss": "^3.3.3"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.30",
"tailwindcss": "^3.3.3",
"vite": "^4.4.5"
}
}
================================================
FILE: URYMosaic/postcss.config.js
================================================
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: URYMosaic/proxyOptions.js
================================================
const common_site_config = require('../../../sites/common_site_config.json');
const { webserver_port } = common_site_config;
export default {
'^/(app|api|assets|files)': {
target: `http://localhost:${webserver_port}`,
ws: true,
router: function(req) {
const site_name = req.headers.host.split(':')[0];
return `http://${site_name}:${webserver_port}`;
}
}
};
================================================
FILE: URYMosaic/src/App.vue
================================================
<template>
<Header/>
<div class="bg-slate-300 min-h-screen"><KOT /></div>
</template>
<script>
import KOT from './components/kot.vue';
import Header from "./components/Header.vue";
export default {
name: "app",
components: {
KOT,
Header,
},
};
</script>
================================================
FILE: URYMosaic/src/components/Header.vue
================================================
<template>
<header class="bg-white p-4 flex justify-between items-center">
<div class="flex items-center">
<img :src="imagePath" alt="Logo" class="ml-20 w-40 h-15 mr-2">
</div>
<div class="flex items-center">
<button class=" hover:bg-slate-300 text-blue font-semibold px-6 py-1 rounded-md" @click="reloadKOT">
<svg class="w-6 h-6 text-blue-800 dark:text-blue" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 1v5h-5M2 19v-5h5m10-4a8 8 0 0 1-14.947 3.97M1 10a8 8 0 0 1 14.947-3.97"/> Refresh
</svg>
</button>
</div>
</header>
</template>
<script>
import uriMosaicImage from "@/assets/logos/mosaic.jpg";
// import KOT from './kot.vue';
export default {
name: "Header",
setup() {
},
data() {
return {
imagePath: uriMosaicImage,
};
},
methods:{
reloadKOT(){
// KOT.methods.fetchKOT(this);
window.location.reload();
}
},
computed: {
},
};
</script>
================================================
FILE: URYMosaic/src/components/kot.vue
================================================
<template>
<div class="mx-auto p-6 mb-16 relative">
<!-- Alert Modal div start-->
<div
v-if="this.showModal"
class="fixed inset-0 z-10 overflow-y-auto bg-gray-100"
>
<div class="flex items-center justify-center">
<div class="w-full rounded-lg bg-white p-6 shadow-lg md:max-w-md">
<p
class="block text-left text-xl font-medium text-gray dark:text-gray"
>
<span
class="w-3 h-3 rounded-full inline-block mr-1 bg-red-500"
></span>
Not Permitted
</p>
<hr class="border-gray-200" />
<p class="text-left text-xl mt-6 font-medium text-gray-500">
Log in to access this page.
</p>
<div class="flex justify">
<button
@click="
this.showModal = false;
this.redirectToLogin();
"
class="mt-8 rounded bg-blue-500 px-3 py-2 text-white hover:bg-blue-600"
>
Login
</button>
</div>
</div>
</div>
</div>
<!-- Alert Modal div end-->
<div
class="grid grid-cols-1 gap-10 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<div v-for="kot in this.kot" :key="kot.name">
<div
:class="[kot.color]"
class="inline-block shadow-lg gap-4 p-3 rounded-2xl w-90 h-auto masonry-item"
style="margin-top: 28px"
v-if="!kot.showDiv && kot.production === production"
>
<div class="w-64 check">
<div
:class="[{ hidden: !kot.isRotated }]"
@click="rotateCard(kot)"
class="absolute inset-0 bg-white z-50 opacity-80 rounded-2xl flex flex-col justify-center items-center"
>
<button
@click="
kot.type === 'Cancelled' || kot.type === 'Partially cancelled'
? confirmOrder(kot)
: serveOrder(kot)
"
:class="[{ hidden: !kot.isRotated }]"
class="py-2 px-6 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition duration-300 ease-in-out"
>
{{
kot.type === "Cancelled" || kot.type === "Partially cancelled"
? "Confirm"
: "Serve"
}}
</button>
</div>
<!-- Serve Button -->
<!-- Card Header: Table Name and Order Number -->
<div class="flex justify-between" @click="rotateCard(kot)">
<div class="text-sm w-48">
<span
v-if="kot.tableortakeaway !== 'Takeaway'"
class="text-sm font-medium text-[#6B7280]"
>Table
</span>
<span class="text-black-500 font-semibold">
{{ kot.tableortakeaway }}
<span class="text-sm font-medium text-[#6B7280]"
>( {{ kot.user }} )</span
></span
><br />
<span v-if="kot.is_aggregator" class="text-sm font-medium text-[#6B7280]">Aggregator</span>
<span v-if="kot.is_aggregator" class="text-black-500 ml-2 font-semibold"
>{{ kot.customer_name }}
</span><br v-if="kot.is_aggregator" />
<span v-if="kot.is_aggregator" class="text-sm font-medium text-[#6B7280]">Aggregator ID</span>
<span v-if="kot.is_aggregator" class="text-black-500 ml-2 font-semibold"
>{{ kot.aggregator_id }}
</span><br v-if="kot.is_aggregator"/>
<span class="text-sm font-medium text-[#6B7280]">Order</span>
<span class="text-black-500 ml-2 font-semibold"
>{{ this.daily_order_number ? kot.order_no : kot.invoice.slice(-4) }}
</span>
<span
class="text-black-500 ml-2 font-semibold"
v-if="
kot.type === 'Partially cancelled' ||
kot.type === 'Cancelled'
"
>
( {{ kot.type }} )</span
>
</div>
<div
:class="kot.timecolor"
class="font-inter font-semibold text-2xl leading-10"
>
{{ kot.timeRemaining }}
</div>
</div>
<div
v-if="kot.type === 'Duplicate'"
class="text-[#DC0000] font-medium"
>
( Duplicate KOT ( CHECK WITH CAPTAIN ) )
</div>
<div v-show="kot.comments" class="text-[#6B7280] font-medium">
( {{ kot.comments }} )
</div>
<div></div>
<div>
<div
class="font-semibold justify-between items-center mt-2"
v-for="kotitem in sortedKotItems(kot)"
:key="kotitem.name"
>
<div
@click="
() => {
toggleItemStrikeThrough(kotitem, kot);
}
"
:class="{
'line-through text-green-700': kotitem.striked,
}"
class="flex font-semibold justify-between items-center"
>
<div>
<span class="ml-2 text-black-100">{{
kotitem.item_name
}}<span v-show="kotitem.indicate_course" class="text-sm text-gray-500 ml-1"> ( {{kotitem.course}} )</span>
</span
><br />
<span
class="ml-2 text-black-100"
v-if="
kot.type === 'Partially cancelled' ||
kot.type === 'Cancelled'
"
>[Old Qty = {{ kotitem.quantity }}]</span
>
</div>
<div>
<span class="ml-2 text-black-100">{{ kotitem.qty }}</span>
</div>
</div>
<div>
<p
v-show="kotitem.comments"
class="ml-2 text-[#6B7280] font-medium"
>
{{ kotitem.comments }}
</p>
<hr class="my-1 border-gray-200 mt-2" />
</div>
</div>
</div>
</div>
<!-- You can add more item/quantity pairs here as needed -->
</div>
</div>
</div>
<!-- Audio Alert Message -->
<div
v-if="showAudioAlertMessage"
class="absolute top-1 left-1/2 transform -translate-x-1/2 p-2 font-bold text-2xl text-red-500 text-center"
>
Audio notifications disabled. Click anywhere to enable.
</div>
<div
v-if="statusMessage"
:class="[
'fixed',
'bottom-10',
'right-10',
'p-4',
'rounded',
'text-white',
{
'bg-green-500': isOnline,
'bg-red-500': !isOnline,
},
]"
@transitionend="handleTransitionEnd"
>
{{ statusMessage }}
</div>
</div>
</template>
<script>
import { FrappeApp } from "frappe-js-sdk";
import Masonry from "masonry-layout";
import io from "socket.io-client";
let host = window.location.hostname;
let port = window.location.port;
let protocol = window.location.protocol;
let url = port ? `${protocol}//${host}:${port}` : `${protocol}//${host}`;
window.globalSiteName = '';
let socket;
async function fetchAndSetSiteName() {
try {
const response = await fetch('/api/method/ury.ury.api.ury_kot_display.get_site_name', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
window.globalSiteName = data.message.site_name;
// console.log('Global Site Name:', window.globalSiteName);
} catch (error) {
console.error('Failed to fetch site name:', error);
}
}
async function initializeSocket() {
await fetchAndSetSiteName();
if (window.globalSiteName) {
let site = window.globalSiteName;
let site_url = `${url}/${site}`;
socket = io(site_url,{ withCredentials: true });
console.log("socket == >",socket)
socket.on('connect_error', (err) => {
console.error("Socket connection error:", err);
});
socket.on('connect', () => {
console.log('Socket connected:', socket.connected);
});
} else {
console.error('Site name is not set. Socket cannot be initialized.');
}
}
initializeSocket(); // Initialize the socket after fetching the site name
const frappe = new FrappeApp(url);
export default {
// inject: ["$auth", "$socket"],
data() {
return {
kot: [],
masonry: null,
call: frappe.call(),
production: "",
branch: "",
kot_channel: "",
clickedItems: new Set(),
struckThroughItems: {},
loggeduser: "",
showModal: false,
kot_alert_time: "",
showAudioAlertMessage: false,
audio_alert: 0,
isOnline: navigator.onLine,
statusMessage: "",
daily_order_number:0
};
},
methods: {
playAlertSound(path) {
var currentDomain = window.location.origin;
var audio_path = currentDomain + path;
const audio = new Audio(audio_path);
audio.play();
},
auth() {
return new Promise((resolve, reject) => {
const auth = frappe.auth();
auth
.getLoggedInUser()
.then((user) => {
this.loggeduser = user;
resolve();
})
.catch((error) => {
console.error(error);
reject(error);
});
});
},
fetchKOT() {
return new Promise((resolve, reject) => {
try {
this.call
.get("ury.ury.api.ury_kot_display.kot_list", {})
.then((result) => {
console.log(result,"..............result")
this.branch = result.message.Branch;
this.kot_alert_time = result.message.kot_alert_time;
this.audio_alert = result.message.audio_alert;
this.daily_order_number = result.message.daily_order_number;
this.kot_channel = `kot_update_${this.branch}_${this.production}`;
this.kot = result.message.KOT;
this.updateQtyColorTable();
this.updateTimeRemaining();
this.masonryLoading();
resolve();
})
.catch((error) => {
console.error(error);
reject(error);
});
} catch (error) {
reject(error);
}
});
},
rotateCard(kot) {
this.masonryLoading();
kot.isRotated = !kot.isRotated;
},
confirmOrder(kot) {
const now = new Date();
this.currentTime = now.toLocaleTimeString();
this.call
.post("ury.ury.api.ury_kot_display.confirm_cancel_kot", {
name: kot.name,
user: this.loggeduser,
})
.then((result) => {
// kot.isHidden = !kot.isHidden;
kot.showDiv = !kot.showDiv;
// this.showDiv = false;
this.removeAllItemsFromLocalStorage(kot);
this.masonryLoading();
})
.catch((error) => console.error(error));
},
async serveOrder(kot) {
const now = new Date();
this.currentTime = now.toLocaleTimeString();
this.call
.post("ury.ury.api.ury_kot_display.serve_kot", {
name: kot.name,
time: this.currentTime,
})
.then((result) => {
// kot.isHidden = !kot.isHidden;
kot.showDiv = !kot.showDiv;
// this.showDiv = false;
this.removeAllItemsFromLocalStorage(kot);
this.masonryLoading();
})
.catch((error) => console.error(error));
},
async orderDelayNotify(kot) {
const now = new Date();
this.currentTime = now.toLocaleTimeString();
this.call
.post(
"ury.ury.api.ury_kot_notification.order_delay_notification",
{
id: kot.name,
}
)
.then((result) => {
// console.log("call backed ", result);
})
.catch((error) => console.error(error));
},
toggleItemStrikeThrough(kotitem, kot) {
kotitem.striked = !kotitem.striked;
localStorage.setItem(
`${kot.name}_${kotitem.name}_strike`,
JSON.stringify(kotitem.striked)
);
},
updateColorandTable(kot, restaurant_table, type, table_takeaway) {
if (restaurant_table === undefined) {
kot.tableortakeaway = "Takeaway";
} else {
if (table_takeaway == 1) {
kot.tableortakeaway = "Takeaway";
} else {
kot.tableortakeaway = restaurant_table;
}
}
if (type == "Order Modified") {
kot.color = "bg-[#FFD493] border border-[#FFC700]";
} else if (type == "Partially cancelled" || type == "Cancelled") {
kot.color = "bg-[#FFD2D2] border border-[#FAA7A7]";
} else if (restaurant_table === undefined || table_takeaway == 1) {
kot.color = "bg-blue-100 border border-blue-200";
} else {
kot.color = "bg-white";
}
console.log(type,".............type")
},
updateQtyColorTable() {
this.kot.forEach((kot) => {
console.log(kot,"kot............")
this.updateColorandTable(
kot,
kot.restaurant_table,
kot.type,
kot.table_takeaway
);
kot.kot_items.forEach((kotitem) => {
const savedState = localStorage.getItem(
`${kot.name}_${kotitem.name}_strike`
);
if (savedState) {
kotitem.striked = JSON.parse(savedState);
}
this.calculateQty(
kotitem,
kotitem.quantity,
kot.type,
kotitem.cancelled_qty
);
});
});
},
calculateQty(kotitem, qty, type, cancelled_qty) {
kotitem.qty = qty;
if (type == "Partially cancelled" || type == "Cancelled") {
kotitem.qty = qty - cancelled_qty;
}
},
removeAllItemsFromLocalStorage(kot) {
// Get all keys in local storage
const keys = Object.keys(localStorage);
// Remove keys that start with `${kot.name}_`
keys.forEach((key) => {
if (key.startsWith(`${kot.name}_`)) {
localStorage.removeItem(key);
}
});
},
updateTimeRemaining() {
// console.log("update time", this.kot_channel);
this.kot.forEach((kot) => {
kot.timeRemaining = this.calculateTimeRemaining(kot.time);
const timeRemaining = kot.timeRemaining.split(":");
const minutes =
parseInt(timeRemaining[0]) * 60 + parseInt(timeRemaining[1]);
if (
minutes === this.kot_alert_time &&
kot.type !== "Cancelled" &&
kot.type !== "Partially cancelled"
) {
this.orderDelayNotify(kot);
}
if (minutes >= this.kot_alert_time) {
kot.timecolor = "text-[#DC0000]";
} else {
kot.timecolor = "text-black";
}
});
},
calculateTimeRemaining(targetTime) {
const currentTime = new Date();
const [targetHours, targetMinutes, targetSeconds] = targetTime.split(":");
const targetDate = new Date(
currentTime.getFullYear(),
currentTime.getMonth(),
currentTime.getDate(),
targetHours,
targetMinutes,
targetSeconds
);
const timeDifference = currentTime - targetDate;
const hoursRemaining = Math.floor(timeDifference / 3600000);
const minutesRemaining = Math.floor((timeDifference % 3600000) / 60000);
return `${hoursRemaining} : ${minutesRemaining}`;
},
fetchkotwithmasonry() {
return this.fetchKOT().then(() => {
this.masonryLoading();
});
},
redirectToLogin() {
var currentDomain = window.location.origin;
window.location.href =
currentDomain + "/login?redirect-to=URYMosaic/" + this.production;
},
masonryLoading() {
this.$nextTick(() => {
this.masonry = new Masonry(this.$el.querySelector(".grid"), {
itemSelector: ".masonry-item",
gutter: 28,
// Other Masonry options can be added here
});
this.masonry.layout();
});
},
hideAudioAlertMessage() {
this.showAudioAlertMessage = false;
},
handleOnline() {
this.isOnline = true;
this.setStatusMessage("You are online");
this.hideStatusMessageAfterDelay();
this.fetchKOT().then(() => {
this.masonryLoading();
});
},
handleOffline() {
this.isOnline = false;
this.setStatusMessage("You are Offline");
},
setStatusMessage(message) {
this.statusMessage = message;
},
hideStatusMessageAfterDelay() {
setTimeout(() => {
this.statusMessage = "";
}, 3000);
},
handleTransitionEnd() {
if (!this.isOnline) {
// Reset the status message after transition end
this.setStatusMessage("");
}
},
},
mounted() {
window.addEventListener("online", this.handleOnline);
window.addEventListener("offline", this.handleOffline);
document.addEventListener("click", this.hideAudioAlertMessage);
const currentUrl = window.location.href;
const parts = currentUrl.split("/");
const production = parts[parts.length - 1];
const decodedProduction = decodeURIComponent(production);
this.production = decodedProduction;
const self = this;
window.addEventListener("resize", this.masonryLoading());
this.masonryLoading();
this.auth()
.then(() => {
self.fetchKOT().then(() => {
if (this.audio_alert === 1) {
this.showAudioAlertMessage = true;
}
socket.on(this.kot_channel, (doc) => {
if (this.audio_alert === 1) {
this.playAlertSound(doc.audio_file);
}
let kottime = localStorage.getItem("kot_time");
if (doc.last_kot_time !== null) {
if (doc.last_kot_time !== kottime) {
this.fetchKOT().then(() => {
this.masonryLoading();
});
}
}
this.kot.unshift(doc.kot);
this.masonryLoading();
this.updateQtyColorTable();
this.updateTimeRemaining();
setTimeout(()=>{
if (doc.kot.type === "Cancelled"){
this.fetchKOT().then(() => {
this.masonryLoading();
});
}
},1500)
localStorage.setItem("kot_time", doc.kot.time);
});
});
})
.catch((error) => {
console.error("Authentication error:", error);
this.showModal = true;
});
setInterval(this.updateTimeRemaining, 60000);
},
beforeDestroy() {
window.removeEventListener("online", this.handleOnline);
window.removeEventListener("offline", this.handleOffline);
document.removeEventListener("click", this.hideAudioAlertMessage);
},
computed: {
sortedKotItems() {
return (kot) => {
return kot.kot_items.sort((a, b) => a.serve_priority - b.serve_priority);
};
},
},
};
</script>
<style>
.bg-gray-100 {
background-color: rgba(0, 0, 0, 0.2);
}
</style>
================================================
FILE: URYMosaic/src/index.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
================================================
FILE: URYMosaic/src/main.js
================================================
import './index.css';
import { createApp, reactive } from "vue";
import App from "./App.vue";
import router from './router';
const app = createApp(App);
// Plugins
app.use(router);
// Global Properties,
// components can inject this
// Configure route gaurds
router.beforeEach(async (to, from, next) => {
if (to.matched.some((record) => !record.meta.isLoginPage)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!auth.isLoggedIn) {
next({ name: 'Login', query: { route: to.path } });
} else {
next();
}
} else {
if (auth.isLoggedIn) {
next({ name: 'Home' });
} else {
next();
}
}
});
app.mount("#app");
================================================
FILE: URYMosaic/src/router/auth.js
================================================
export default [
{
path: '/login',
name: 'Login',
component: () =>
import(/* webpackChunkName: "login" */ '../views/Login.vue'),
meta: {
isLoginPage: true
},
props: true
}
]
================================================
FILE: URYMosaic/src/router/index.js
================================================
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
import authRoutes from './auth';
import KOT from '../components/kot.vue';
const routes = [
{
path: "/",
name: "KOT",
component: KOT,
},
...authRoutes,
];
const router = createRouter({
base: "/URYMosaic/",
history: createWebHistory(),
routes,
});
export default router;
================================================
FILE: URYMosaic/src/style.css
================================================
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
================================================
FILE: URYMosaic/src/views/Home.vue
================================================
<template>
<div>
<h1>Home Page</h1>
<!-- Fetch the resource on click -->
<button @click="$resources.ping.fetch()">Ping</button>
</div>
</template>
<script>
export default {
resources: {
ping() {
return {
method: "frappe.ping", // Method to call on backend
onSuccess(d) {
alert(d);
},
};
},
},
};
</script>
================================================
FILE: URYMosaic/src/views/Login.vue
================================================
<template>
<div class="min-h-screen bg-white flex">
<div class="mx-auto w-full max-w-sm lg:w-96">
<form @submit.prevent="login" class="space-y-6">
<label for="email"> Username: </label>
<input type="text" v-model="email" />
<br />
<label for="password"> Password: </label>
<input type="password" v-model="password" />
<button
class="bg-blue-500 block text-white p-2 hover:bg-blue-700"
type="submit"
>
Sign in
</button>
</form>
</div>
</div>
</template>
<script>
export default {
data() {
return {
email: null,
password: null,
};
},
inject: ["$auth"],
async mounted() {
if (this.$route?.query?.route) {
this.redirect_route = this.$route.query.route;
this.$router.replace({ query: null });
}
},
methods: {
async login() {
if (this.email && this.password) {
let res = await this.$auth.login(this.email, this.password);
if (res) {
this.$router.push({ name: "Home" });
}
}
},
},
};
</script>
================================================
FILE: URYMosaic/tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{html,jsx,tsx,vue,js,ts}"],
theme: {
extend: {
spacing: {
'28': '28px', // Define a custom margin-top value
},
},
},
plugins: [],
}
================================================
FILE: URYMosaic/vite.config.js
================================================
import path from 'path';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import proxyOptions from './proxyOptions';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
port: 8080,
proxy: proxyOptions
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
build: {
outDir: '../ury/public/URYMosaic',
emptyOutDir: true,
target: 'es2015',
// rollupOptions: {
// external: ["../../assets/alert/MA_Designed_ModifiedGunBlasts_4.wav"],
// },
},
});
================================================
FILE: package.json
================================================
{
"private": true,
"scripts": {
"ury-pos-install": "cd urypos && yarn install --check-files",
"ury-pos-build": "cd urypos && yarn build",
"ury-mosaic-install": "cd URYMosaic && yarn install --check-files",
"ury-mosaic-build": "cd URYMosaic && yarn build",
"ury-posv2-install": "cd pos && yarn install --check-files",
"ury-posv2-build": "cd pos && yarn build",
"postinstall": "yarn ury-pos-install && yarn ury-mosaic-install && yarn ury-posv2-install",
"build": "yarn ury-pos-build && yarn ury-mosaic-build && yarn ury-posv2-build"
},
"devDependencies": {
"@types/qz-tray": "^2.2.2"
}
}
================================================
FILE: pos/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: pos/AGENTS.MD
================================================
# POS Frontend — Agent Documentation
## 1. Overview
This is the **URY POS v2** — a React 19 single-page application that serves as the primary point-of-sale interface for the URY restaurant management system. It runs as a Frappe web page served at `/pos` and communicates exclusively with the Frappe/ERPNext backend via REST API calls.
**Responsibilities:**
- Taking and managing restaurant orders (Dine-In, Takeaway, Delivery/Aggregator)
- Real-time menu display with category/course filtering
- Cart management with item customization (variants, add-ons, comments)
- Payment processing with multi-mode split payment support
- Order history viewing, editing, and cancellation
- Table management for dine-in
- Invoice printing (HTML and thermal via QZ Tray)
- POS opening/closing session tracking
---
## 2. Tech Stack
| Concern | Library / Version |
|---|---|
| UI framework | React 19.0.0 + TypeScript 5.7.2 |
| Build tool | Vite 6.2.0 |
| State management | Zustand 5.0.6 |
| Routing | React Router DOM 6.30.1 |
| Styling | Tailwind CSS 3.4.17 |
| Backend API | frappe-js-sdk 1.10.0 |
| Notifications | react-toastify 11.0.5 |
| Thermal printing | qz-tray 2.2.5 |
| Icons | lucide-react |
| Accessible UI primitives | @radix-ui/react-select |
Build output is written to `../ury/public/pos/` and the HTML entry point is `../ury/www/pos.html`.
---
## 3. Project Structure
```
pos/
├── src/
│ ├── main.tsx # Entry point — initialises i18n then mounts React
│ ├── App.tsx # Root component, routing, auth guard
│ ├── index.css # Global Tailwind imports
│ │
│ ├── pages/
│ │ ├── POS.tsx # Main ordering interface (menu + order panel)
│ │ ├── Orders.tsx # Order history, cancel, edit, print, pay
│ │ └── Table.tsx # Table layout spatial view
│ │
│ ├── components/ # All UI components
│ │ ├── ui/ # Headless/primitive components (Button, Input, Dialog…)
│ │ ├── Header.tsx # Top bar: logo, search, user menu
│ │ ├── Sidebar.tsx # Left nav: menu categories / courses
│ │ ├── MenuList.tsx # Grid of MenuCard items
│ │ ├── MenuCard.tsx # Single menu item card
│ │ ├── OrderPanel.tsx # Right panel: cart, totals, submit
│ │ ├── PaymentDialog.tsx # Split-payment modal
│ │ ├── ProductDialog.tsx # Item variant/add-on customisation modal
│ │ ├── CustomerSelect.tsx# Customer search + new customer form
│ │ ├── OrderTypeSelect.tsx # Dine In / Takeaway / Aggregators toggle
│ │ ├── AggregatorSelect.tsx# Aggregator platform selector
│ │ ├── TableSelectionDialog.tsx # Table picker for dine-in
│ │ ├── CommentDialog.tsx # Order-level comment editor
│ │ ├── POSOpeningDialog.tsx # POS not opened / not closed error screen
│ │ ├── POSOpeningProvider.tsx # Checks POS opening entry on mount
│ │ ├── OrderStatusSidebar.tsx # Status filter tabs (Draft, Paid…)
│ │ ├── AuthGuard.tsx # Redirects unauthenticated users
│ │ ├── InitialLoader.tsx # Splash screen during boot
│ │ ├── Spotlight.tsx # Keyboard shortcut overlay
│ │ └── LayoutView.tsx # Spatial table layout renderer
│ │
│ ├── store/
│ │ ├── pos-store.ts # Primary Zustand store (menu, cart, order state)
│ │ ├── root-store.ts # Root store (user, orders list, search)
│ │ └── slices/
│ │ ├── auth-slice.ts
│ │ ├── config-slice.ts
│ │ └── orders-slice.ts
│ │
│ ├── lib/ # API wrappers and utilities
│ │ ├── frappe-sdk.ts # Frappe SDK instance (call, db, auth)
│ │ ├── menu-api.ts # getRestaurantMenu, getAggregatorMenu
│ │ ├── order-api.ts # syncOrder, cancelOrder, getTableOrder
│ │ ├── payment-api.ts # getPaymentModes
│ │ ├── invoice-api.ts # printInvoice, makePayment
│ │ ├── pos-profile-api.ts# getCombinedPosProfile, getCurrencyInfo
│ │ ├── pos-opening-api.ts# checkPOSOpening
│ │ ├── customer-api.ts # searchCustomers, addCustomer
│ │ ├── aggregator-api.ts # getAggregators
│ │ ├── table-api.ts # getTables, getRooms
│ │ ├── print.ts # HTML invoice printing
│ │ ├── print-qz.ts # QZ Tray thermal print
│ │ ├── role-utils.ts # Role permission checks
│ │ ├── utils.ts # formatCurrency, cn (classnames)
│ │ └── storage.ts # localStorage/sessionStorage helpers
│ │
│ ├── data/
│ │ ├── order-types.ts # DINE_IN, TAKEAWAY, AGGREGATORS constants
│ │ ├── doctypes.ts # Frappe doctype name constants
│ │ └── menu-data.ts # Static menu structure types
│ │
│ └── i18n/ # Internationalisation system (see §7)
│ ├── index.ts
│ ├── config.ts
│ ├── loader.ts
│ ├── resolve-language.ts
│ └── locales/
│ ├── en.json
│ └── fr.json
│
├── public/ # Static assets (favicon, logos)
├── index.html # HTML entry point
├── vite.config.ts
├── tailwind.config.js
└── tsconfig.json
```
---
## 4. Component Architecture
**Patterns used:**
- **Container / presentational split** — pages (POS.tsx, Orders.tsx) own data-fetching and state; components receive props or subscribe to stores.
- **Zustand stores** — global state is in `pos-store` (POS session) and `root-store` (auth + orders). Components use `usePOSStore()` and `useRootStore()` hooks.
- **No prop-drilling beyond one level** — components access the store directly rather than having state threaded through many levels.
- **Dialog pattern** — modals (PaymentDialog, ProductDialog, CommentDialog) are rendered conditionally with a boolean flag and mounted/unmounted, not hidden via CSS.
**Reusability approach:**
- `ui/` components are headless: they apply className merging via `cn()` but carry no business logic.
- Business components (e.g., `CustomerSelect`) contain their own local state and API calls when the logic is highly specific to that widget.
---
## 5. Data Flow
```
Frappe REST API
│
▼
lib/*.ts API wrappers (call.get / call.post / fetch)
│
▼
Zustand stores (pos-store, root-store)
│
├── pos-store: menu items, cart (activeOrders), POS profile,
│ selected table/customer/order type, payment modes
│
└── root-store: current user, orders list (pagination),
selected order detail, search query
│
▼
React components subscribe via hooks
│
▼
UI renders
```
**Key flows:**
1. **Boot:** `main.tsx` → `initI18n()` → `App.tsx` → `AuthGuard` checks session → `POSOpeningProvider` checks POS opening entry → store hydrates menu + profile.
2. **Add to cart:** click `MenuCard` → `usePOSStore().addToOrder()` → re-render `OrderPanel`.
3. **Submit order:** `OrderPanel.handleSubmit` → validate → `syncOrder()` → Frappe API → `resetOrderState()`.
4. **Payment:** `Orders.tsx` opens `PaymentDialog` → `call.post(make_invoice)` → `fetchOrders()`.
---
## 6. API Integration
**SDK initialisation** (`lib/frappe-sdk.ts`):
```typescript
const frappe = new FrappeApp(import.meta.env.VITE_FRAPPE_BASE_URL);
export const call = frappe.call(); // RPC-style POST/GET
export const db = frappe.db(); // CRUD
export const auth = frappe.auth(); // login/logout/getLoggedInUser
```
**Calling a whitelisted Python method:**
```typescript
// GET
call.get('ury.ury_pos.api.getRestaurantMenu', { pos_profile, room })
// POST
call.post('ury.ury.doctype.ury_order.ury_order.sync_order', orderData)
```
**Error handling pattern:**
Frappe wraps server errors in `_server_messages` (double-JSON-encoded string). Components parse this:
```typescript
const messages = JSON.parse(error._server_messages);
const msg = JSON.parse(messages[0]).message;
```
**Auth / session:** Session cookie is managed by Frappe. No JWT. The SDK's `auth.getLoggedInUser()` call validates the session; failure redirects to `/login?redirect-to=%2Fpos`.
---
## 7. i18n System
### How it works
Translations live in `src/i18n/locales/*.json`. A lightweight custom engine (no external library) provides:
- `t(key)` — resolves a dot-notation key against the active locale map.
- `t(key, params)` — interpolates `{{placeholder}}` tokens.
- Falls back to the key string itself if a translation is missing (visible but non-breaking).
### Initialisation
In `main.tsx`, before React mounts:
```typescript
import { initI18n } from './i18n';
initI18n().then(() => ReactDOM.createRoot(...).render(...));
```
`initI18n()` calls `resolveLanguage()` which checks (in priority order):
1. `window.frappe.boot.lang` — Frappe/ERPNext system language
2. `localStorage.getItem('ury_language')` — manual override
3. `'en'` — default fallback
### File locations
```
src/i18n/
├── config.ts — DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES map
├── loader.ts — dynamic import() with per-language cache
├── resolve-language.ts — priority-based language resolution
├── index.ts — t(), initI18n(), getActiveLanguage()
└── locales/
├── en.json — English (source of truth)
└── fr.json — French
```
### Key namespaces in locale files
| Namespace | Contents |
|---|---|
| `common` | Loading, Cancel, Save, Apply, Change… |
| `header` | Search placeholders, Switch To Desk, Logout… |
| `menu` | Filter labels (All, Special Items) |
| `cart` | Empty state, button labels, hints |
| `order` | Order detail labels, cancel dialog |
| `payment` | Payment dialog labels, discount, summary |
| `pos` | POS opening/closing dialog |
| `customer` | Customer search and form labels |
| `comment` | Comment dialog |
| `errors` | All error messages |
| `success` | All success/info messages |
### Adding a new language
1. Create `src/i18n/locales/<lang-code>.json` copying the structure of `en.json`.
2. Translate all values (do not change keys).
3. Add the language to `SUPPORTED_LANGUAGES` in `config.ts`:
```typescript
export const SUPPORTED_LANGUAGES = { en: 'English', fr: 'Français', de: 'Deutsch' };
```
4. The loader will dynamically import the file on demand. No other changes needed.
---
## 8. Design Principles
**Naming conventions:**
- Components: PascalCase, one component per file named identically.
- Zustand stores: `use<Name>Store` hooks, state slices in `store/slices/`.
- API wrappers: `lib/<domain>-api.ts`, exported as named functions (`getX`, `createX`, `syncX`).
- Constants: SCREAMING_SNAKE_CASE in `data/` files.
- CSS: Tailwind utility classes only; no custom CSS except in `index.css` and `ui/toast.css`.
**Component boundaries:**
- Components do not import from other components' subdirectories — they import from `../components/<Name>` or `../components/ui`.
- Store slices are combined in `root-store.ts` — add new slices there, not as separate stores.
**State handling rules:**
- POS session state (cart, menu, profile) → `pos-store`.
- Cross-page state (user, orders list, search) → `root-store`.
- Transient UI state (dialog open, loading flag) → component `useState`.
- Never store derived values in state — compute them in component or via selectors.
---
## 9. Guidelines for AI Agents
### Safe to modify
- Adding new `t()` keys: add to `en.json` and `fr.json` simultaneously, then use in component.
- UI text changes: always go through `t()`, never hardcode.
- Adding new components: follow the existing pattern — named export, TypeScript props interface, Zustand for data.
- Adding new API wrappers: add to the appropriate `lib/*-api.ts` file.
- Styling changes: Tailwind classes only.
### How to add a new feature
1. Define new state in the appropriate store slice.
2. Add API call in `lib/`.
3. Create or modify component.
4. Add translation keys to both `en.json` and `fr.json`.
5. No need to modify `main.tsx`, `App.tsx`, or `vite.config.ts` for typical features.
### What NOT to break
- **`frappe-sdk.ts`** — do not change how `call`, `db`, `auth` are exported; every API file depends on them.
- **`pos-store.ts` `resetOrderState()`** — called after every successful order submit/payment; must reset all transient order fields.
- **`main.tsx` init sequence** — `initI18n()` must resolve before `ReactDOM.render()`.
- **`AuthGuard`** — do not bypass or remove; it protects the entire app.
- **`POSOpeningProvider`** — do not remove; it prevents use of POS without a valid opening entry.
- **Order type constants** (`DINE_IN`, `TAKEAWAY`, `AGGREGATORS`) — these are used in API payloads sent to Frappe; do not rename without updating the backend.
- **`uniqueId` on cart items** — used as React keys and for `updateQuantity`/`removeFromOrder`; must remain stable per cart item.
### Common pitfalls
- Frappe API errors come in `error._server_messages` (double-encoded JSON), not `error.message`. Use the existing error parsing pattern in `OrderPanel.tsx`.
- `posProfile` can be `null` during initialisation. Always null-check before reading `.name`, `.cashier`, etc.
- QZ Tray printing requires `qz_print === 1` on the POS profile. Check `posProfile.qz_print` before calling `print-qz.ts`.
- Do not add dependencies to `package.json` without confirming the build still outputs to `../ury/public/pos/`.
================================================
FILE: pos/CLAUDE.MD
================================================
AGENTS.MD
================================================
FILE: pos/README.md
================================================
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
```js
export default tseslint.config({
extends: [
// Remove ...tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default tseslint.config({
plugins: {
// Add the react-x and react-dom plugins
'react-x': reactX,
'react-dom': reactDom,
},
rules: {
// other rules...
// Enable its recommended typescript rules
...reactX.configs['recommended-typescript'].rules,
...reactDom.configs.recommended.rules,
},
})
```
================================================
FILE: pos/eslint.config.js
================================================
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
================================================
FILE: pos/index.html
================================================
<!doctype html>
{% set user_lang = frappe.lang or 'en' %}
{% set is_rtl = user_lang in ['ar', 'he', 'fa', 'ur', 'ku'] %}
<html lang="{{ user_lang }}" dir="{{ 'rtl' if is_rtl else 'ltr' }}">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/ury.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<title>URY POS</title>
</head>
<body>
<div id="root"></div>
<script>
window.csrf_token = "{{ csrf_token }}";
if (!window.frappe) window.frappe = {};
window.app_name = "{{ app_name }}";
frappe.boot = JSON.parse({{ boot }});
</script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
================================================
FILE: pos/package.json
================================================
{
"name": "pos",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --base=/assets/ury/pos/ && yarn copy-html-entry",
"copy-html-entry": "cp ../ury/public/pos/index.html ../ury/www/pos.html",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-select": "^2.2.5",
"@tailwindcss/postcss": "^4.1.11",
"@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.21",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"frappe-js-sdk": "^1.10.0",
"jsrsasign": "^11.1.0",
"lucide-react": "^0.525.0",
"postcss": "^8.5.6",
"qz-tray": "^2.2.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^6.30.1",
"react-toastify": "^11.0.5",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^3.4.17",
"uuid": "^11.1.0",
"zustand": "^5.0.6"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/node": "^24.0.10",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
}
}
================================================
FILE: pos/postcss.config.js
================================================
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: pos/privateKey.js
================================================
export const privateKey = `PASTE_YOUR_PRIVATE_KEY_HERE`;
================================================
FILE: pos/src/App.tsx
================================================
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Footer from './components/Footer';
import Header from './components/Header';
import Orders from './pages/Orders';
import POS from './pages/POS';
import Table from './pages/Table';
import AuthGuard from './components/AuthGuard';
import POSOpeningProvider from './components/POSOpeningProvider';
import ScreenSizeProvider from './components/ScreenSizeProvider';
import { ToastProvider } from './components/ui/toast';
import { usePOSStore } from './store/pos-store';
import { useEffect } from 'react';
import { getActiveLanguage } from './i18n';
function App() {
const {
initializeApp
} = usePOSStore();
useEffect(() => {
initializeApp();
}, [initializeApp]);
useEffect(() => {
const lang = getActiveLanguage();
const isRtl = ['ar', 'he', 'fa', 'ur', 'ku'].includes(lang);
document.documentElement.dir = isRtl ? 'rtl' : 'ltr';
document.documentElement.lang = lang || 'en';
}, []);
return (
<>
<ToastProvider />
<ScreenSizeProvider>
<AuthGuard>
<POSOpeningProvider>
<Router basename="/pos">
<div className="flex flex-col h-screen bg-gray-100 font-inter">
<Header />
<div className="flex-1 overflow-hidden">
<Routes>
<Route path="/" element={<POS/>} />
<Route path="/orders" element={<Orders />} />
<Route path="/table" element={<Table />} />
</Routes>
</div>
<Footer />
</div>
</Router>
</POSOpeningProvider>
</AuthGuard>
</ScreenSizeProvider>
</>
);
}
export default App;
================================================
FILE: pos/src/components/AggregatorSelect.tsx
================================================
import { useEffect, useState } from 'react';
import { usePOSStore } from '../store/pos-store';
import { Select, SelectItem } from './ui/select';
import { getAggregators, type Aggregator } from '../lib/aggregator-api';
interface AggregatorSelectProps {
disabled?: boolean;
}
export function AggregatorSelect({ disabled }: AggregatorSelectProps) {
const { selectedAggregator, setSelectedAggregator, fetchAggregatorMenu } = usePOSStore();
const [aggregators, setAggregators] = useState<Aggregator[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchAggregatorsList = async () => {
setLoading(true);
try {
const data = await getAggregators();
setAggregators(data);
} catch (error) {
console.error('Failed to fetch aggregators:', error);
} finally {
setLoading(false);
}
};
fetchAggregatorsList();
}, []);
const handleAggregatorChange = async (value: string) => {
const aggregator = aggregators.find(a => a.customer === value);
setSelectedAggregator(aggregator || null);
if (aggregator) {
await fetchAggregatorMenu(aggregator.customer);
}
};
return (
<div>
<Select
value={selectedAggregator?.customer || ''}
onValueChange={handleAggregatorChange}
disabled={disabled || loading}
placeholder={loading ? 'Loading aggregators...' : 'Select an aggregator'}
>
{aggregators.map((aggregator) => (
<SelectItem
key={aggregator.customer}
value={aggregator.customer}
className="capitalize"
>
{aggregator.customer}
</SelectItem>
))}
</Select>
</div>
);
}
================================================
FILE: pos/src/components/AuthGuard.tsx
================================================
import React, { useEffect, useState } from 'react';
import { useRootStore } from '../store/root-store';
import { Button } from './ui/button';
import { Spinner } from './ui/spinner';
import { RefreshCw } from 'lucide-react';
interface Props {
children: React.ReactNode;
}
const AuthGuard: React.FC<Props> = ({ children }) => {
const {
checkAuth,
user,
isLoading: authLoading,
error: authError,
fetchPosProfile,
posProfile,
isLoading: configLoading,
error: configError,
hasAccess,
} = useRootStore();
// State to track if we're rechecking permissions
const [isRechecking, setIsRechecking] = useState(false);
useEffect(() => {
// Start auth check
checkAuth();
}, [checkAuth]);
// Once we have a user, fetch POS profile
useEffect(() => {
if (user) {
fetchPosProfile();
}
}, [user, fetchPosProfile]);
// Show loading state while either auth or config is loading
if (authLoading || (user && configLoading) || isRechecking) {
return (
<div className="min-h-screen">
<Spinner />
</div>
);
}
if (authError || configError) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="text-red-600 text-xl mb-4">⚠️</div>
<h2 className="text-xl font-semibold text-gray-800 mb-2">Access Denied</h2>
<p className="text-gray-600">{authError || configError}</p>
</div>
</div>
);
}
if (!user) {
// The checkAuth function will handle the redirect to login
return null;
}
if (!posProfile) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="text-amber-600 text-xl mb-4">⚠️</div>
<h2 className="text-xl font-semibold text-gray-800 mb-2">Configuration Error</h2>
<p className="text-gray-600">POS Profile not found or not configured.</p>
</div>
</div>
);
}
if (!hasAccess) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="text-amber-600 text-xl mb-4">🔒</div>
<h2 className="text-xl font-semibold text-gray-800 mb-2">Permission Required</h2>
<p className="text-gray-600">You do not have permission to access this application.</p>
<p className="text-sm text-gray-500 mt-2">Required roles: {posProfile.role_allowed_for_billing.map(r => r.role).join(', ')}</p>
<Button
variant="outline"
className="mt-4"
onClick={async () => {
setIsRechecking(true);
try {
await fetchPosProfile(true); // Force refresh the POS profile
} finally {
setIsRechecking(false);
}
}}
>
<RefreshCw className="w-4 h-4 mr-2" />
Recheck Permissions
</Button>
</div>
</div>
);
}
return <>{children}</>;
};
export default AuthGuard;
================================================
FILE: pos/src/components/CommentDialog.tsx
================================================
import { useState } from 'react';
import { MessageSquare, X } from 'lucide-react';
import { Button } from './ui';
import { t } from '../i18n';
interface CommentDialogProps {
isOpen: boolean;
onClose: () => void;
onSave: (comment: string) => void;
initialComment?: string;
}
const CommentDialog = ({ isOpen, onClose, onSave, initialComment = '' }: CommentDialogProps) => {
const [comment, setComment] = useState(initialComment);
const handleSave = () => {
onSave(comment);
onClose();
};
const handleCancel = () => {
setComment(initialComment);
onClose();
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4 shadow-xl">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<MessageSquare className="w-5 h-5 text-blue-600" />
<h2 className="text-lg font-semibold text-gray-900">
{t('comment.title')}
</h2>
</div>
<Button
onClick={handleCancel}
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
>
<X className="w-4 h-4" />
</Button>
</div>
<div className="mb-6">
<label htmlFor="comment" className="block text-sm font-medium text-gray-700 mb-2">
{t('comment.label')}
</label>
<textarea
id="comment"
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder={t('comment.placeholder')}
className="w-full h-32 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none"
autoFocus
/>
</div>
<div className="flex gap-3 justify-end">
<Button
onClick={handleCancel}
variant="outline"
className="px-4 py-2"
>
{t('common.cancel')}
</Button>
<Button
onClick={handleSave}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700"
>
{t('comment.save_button')}
</Button>
</div>
</div>
</div>
);
};
export default CommentDialog;
================================================
FILE: pos/src/components/CustomerSelect.tsx
================================================
import { useState, useRef, useEffect } from 'react';
import { UserPlus, Mail, Phone, Loader } from 'lucide-react';
import { usePOSStore, type Customer } from '../store/pos-store';
import { Button, Dialog, DialogContent, Input } from './ui';
import { Select, SelectItem } from './ui';
import { ChevronDown } from 'lucide-react';
import React from 'react';
import { addCustomer, type CreateCustomerData, searchCustomers } from '../lib/customer-api';
import { AggregatorSelect } from './AggregatorSelect';
import { t } from '../i18n';
// NewCustomerForm component
function NewCustomerForm({
onClose,
onSuccess,
isCreatingCustomer: parentIsCreatingCustomer,
setIsCreatingCustomer: setParentIsCreatingCustomer,
prefillName = '',
prefillPhone = ''
}: {
onClose: () => void;
onSuccess?: () => void;
isCreatingCustomer?: boolean;
setIsCreatingCustomer?: React.Dispatch<React.SetStateAction<boolean>>;
prefillName?: string;
prefillPhone?: string;
}) {
const { customerGroups, territories, fetchCustomerGroups, fetchTerritories, setSelectedCustomer } = usePOSStore();
const [newCustomerName, setNewCustomerName] = React.useState('');
const [newCustomerPhone, setNewCustomerPhone] = React.useState('');
const [newCustomerGroup, setNewCustomerGroup] = React.useState("");
const [newCustomerTerritory, setNewCustomerTerritory] = React.useState("");
const [formError, setFormError] = React.useState(false);
const [apiError, setApiError] = React.useState<string>("");
const [loadingGroups, setLoadingGroups] = React.useState(false);
const [loadingTerritories, setLoadingTerritories] = React.useState(false);
// Use parent loading state if available, otherwise fallback to local state
const [localIsCreatingCustomer, setLocalIsCreatingCustomer] = React.useState(false);
const isCreatingCustomer = parentIsCreatingCustomer ?? localIsCreatingCustomer;
const setIsCreatingCustomer = setParentIsCreatingCustomer ?? setLocalIsCreatingCustomer;
// Handle prefill values
React.useEffect(() => {
if (prefillName) {
setNewCustomerName(prefillName);
}
if (prefillPhone) {
setNewCustomerPhone(prefillPhone);
}
}, [prefillName, prefillPhone]);
// Fetch groups/territories on mount
React.useEffect(() => {
if (!customerGroups.length) {
setLoadingGroups(true);
fetchCustomerGroups().finally(() => setLoadingGroups(false));
}
if (!territories.length) {
setLoadingTerritories(true);
fetchTerritories().finally(() => setLoadingTerritories(false));
}
}, []);
async function handleAddCustomerSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
if (!newCustomerName || !newCustomerPhone) {
setFormError(true);
return;
}
setFormError(false);
setApiError("");
setIsCreatingCustomer(true);
try {
const customerData: CreateCustomerData = {
customer_name: newCustomerName.trim(),
mobile_number: newCustomerPhone.trim(),
};
// Add optional fields only if they have values
if (newCustomerGroup) {
customerData.customer_group = newCustomerGroup;
}
if (newCustomerTerritory) {
customerData.territory = newCustomerTerritory;
}
const response = await addCustomer(customerData);
const created = response.data;
// Set selected customer in POS store
setSelectedCustomer({
id: created.name,
name: created.customer_name,
phone: created.mobile_number,
});
// Reset form on success
setNewCustomerName("");
setNewCustomerPhone("");
setNewCustomerGroup("");
setNewCustomerTerritory("");
if (onSuccess) onSuccess();
onClose();
} catch (error: any) {
console.error('Failed to create customer:', error);
setApiError(error?.message || t('customer.failed_create'));
} finally {
setIsCreatingCustomer(false);
}
}
return (
<form className="space-y-4" onSubmit={handleAddCustomerSubmit}>
{apiError && (
<div className="bg-red-50 border border-red-200 rounded-md p-3">
<div className="text-sm text-red-600">{apiError}</div>
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1" htmlFor="new-customer-name">{t('customer.name_label')} <span className="text-red-500">*</span></label>
<Input
id="new-customer-name"
type="text"
value={newCustomerName}
onChange={e => setNewCustomerName(e.target.value)}
required
disabled={isCreatingCustomer}
aria-invalid={!!formError && !newCustomerName}
/>
{formError && !newCustomerName && (
<div className="text-xs text-red-500 mt-1">{t('customer.name_required')}</div>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1" htmlFor="new-customer-phone">{t('customer.phone_label')} <span className="text-red-500">*</span></label>
<div className="relative">
<Input
id="new-customer-phone"
type="tel"
value={newCustomerPhone}
onChange={e => setNewCustomerPhone(e.target.value)}
required
disabled={isCreatingCustomer}
className="pl-10"
aria-invalid={!!formError && !newCustomerPhone}
/>
<Phone className="absolute left-3 top-2.5 text-gray-400 w-5 h-5" />
</div>
{formError && !newCustomerPhone && (
<div className="text-xs text-red-500 mt-1">{t('customer.phone_required')}</div>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">{t('customer.customer_group_label')}</label>
<Select
placeholder={loadingGroups ? t('common.loading') : t('customer.select_group')}
value={newCustomerGroup}
onValueChange={setNewCustomerGroup}
disabled={isCreatingCustomer || loadingGroups || !customerGroups.length}
>
{customerGroups.map((group) => (
<SelectItem key={group} value={group} className="capitalize">
{group}
</SelectItem>
))}
</Select>
{!loadingGroups && !customerGroups.length && (
<div className="text-xs text-gray-400 mt-1">{t('common.no_options')}</div>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">{t('customer.territory_label')}</label>
<Select
placeholder={loadingTerritories ? t('common.loading') : t('customer.select_territory')}
value={newCustomerTerritory}
onValueChange={setNewCustomerTerritory}
disabled={isCreatingCustomer || loadingTerritories || !territories.length}
>
{territories.map((territory) => (
<SelectItem key={territory} value={territory} className="capitalize">
{territory}
</SelectItem>
))}
</Select>
{!loadingTerritories && !territories.length && (
<div className="text-xs text-gray-400 mt-1">{t('common.no_options')}</div>
)}
</div>
<div className="flex gap-3 mt-6">
<Button
type="submit"
variant="default"
className="flex-1"
disabled={isCreatingCustomer}
>
{isCreatingCustomer ? (
<>
<Loader className="w-4 h-4 mr-2 animate-spin" />
{t('customer.adding')}
</>
) : (
t('customer.add_button')
)}
</Button>
<Button
type="button"
variant="outline"
onClick={onClose}
disabled={isCreatingCustomer}
>
{t('common.cancel')}
</Button>
</div>
</form>
);
}
interface CustomerSelectProps {
disabled?: boolean;
}
export function CustomerSelect({ disabled }: CustomerSelectProps) {
const { selectedCustomer, setSelectedCustomer, selectedOrderType, isUpdatingOrder } = usePOSStore();
const [showNewCustomerForm, setShowNewCustomerForm] = useState(false);
const [isCreatingCustomer, setIsCreatingCustomer] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [isOpen, setIsOpen] = useState(false);
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
const [searchResults, setSearchResults] = useState<any[]>([]);
const [isSearching, setIsSearching] = useState(false);
const [searchError, setSearchError] = useState<string | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [prefillName, setPrefillName] = useState('');
const [prefillPhone, setPrefillPhone] = useState('');
// Debounced search
useEffect(() => {
if (!isOpen || !searchTerm.trim()) {
setSearchResults([]);
setSearchError(null);
setIsSearching(false);
return;
}
setIsSearching(true);
setSearchError(null);
const handler = setTimeout(() => {
searchCustomers(searchTerm)
.then(results => {
setSearchResults(results);
setIsSearching(false);
})
.catch(err => {
setSearchError(t('customer.failed_search'));
setIsSearching(false);
});
}, 300);
return () => clearTimeout(handler);
}, [searchTerm, isOpen]);
// Handle keyboard navigation
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (!isOpen && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
setIsOpen(true);
setHighlightedIndex(0);
return;
}
if (e.key === 'ArrowDown') {
setHighlightedIndex((prev) => Math.min(prev + 1, searchResults.length));
e.preventDefault();
} else if (e.key === 'ArrowUp') {
setHighlightedIndex((prev) => Math.max(prev - 1, 0));
e.preventDefault();
} else if (e.key === 'Enter') {
if (isOpen) {
if (highlightedIndex === searchResults.length) {
setShowNewCustomerForm(true);
setIsOpen(false);
} else if (searchResults[highlightedIndex]) {
// The API returns { name, content, ... }
const customer = searchResults[highlightedIndex];
setSelectedCustomer({
id: customer.name,
name: customer.content?.match(/Customer Name : ([^|]+)/)?.[1]?.trim() || customer.name,
phone: customer.content?.match(/Mobile Number : ([^|]+)/)?.[1]?.trim() || '',
});
setSearchTerm('');
setIsOpen(false);
}
}
} else if (e.key === 'Escape') {
setIsOpen(false);
}
};
if (selectedOrderType === 'Aggregators') {
return <AggregatorSelect />;
}
return (
<div className="relative">
{selectedCustomer ? (
<div className="flex items-center justify-between bg-blue-50 p-3 rounded-lg">
<div>
<p className="font-medium text-blue-900">{selectedCustomer.name}</p>
<p className="text-sm text-blue-700">{selectedCustomer.phone}</p>
</div>
<Button
onClick={() => setSelectedCustomer(null)}
disabled={isUpdatingOrder}
variant="ghost"
size="sm"
className="text-blue-700 hover:text-blue-800"
>
{t('common.change')}
</Button>
</div>
) : (
<div className="relative">
<div className="flex items-center relative">
<input
ref={inputRef}
type="text"
value={searchTerm}
onChange={e => {
setSearchTerm(e.target.value);
setIsOpen(true);
setHighlightedIndex(0);
}}
onFocus={() => setIsOpen(true)}
onBlur={e => {
setTimeout(() => setIsOpen(false), 100);
}}
onKeyDown={handleKeyDown}
placeholder={t('customer.search_placeholder')}
className="w-full h-10 border border-gray-200 rounded-lg px-4 py-2 text-sm font-medium text-gray-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition-colors"
aria-label={t('customer.search_placeholder')}
autoComplete="off"
/>
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" />
</div>
{isOpen && (
<div className="absolute w-full mt-2 bg-white border border-gray-200 rounded-lg shadow-lg z-50 max-h-80 overflow-y-auto">
{searchTerm.trim() === '' && !isSearching && !searchError && (
<div className="p-4 text-center text-gray-400 text-sm select-none">{t('customer.type_to_search')}</div>
)}
{isSearching && (
<div className="flex items-center justify-center p-4 text-gray-500 text-sm select-none">
<Loader className="w-4 h-4 mr-2 animate-spin" /> {t('common.searching')}
</div>
)}
{searchError && (
<div className="p-4 text-center text-red-500 text-sm select-none">{searchError}</div>
)}
{!isSearching && !searchError && searchResults.length > 0 && searchResults.map((customer, idx) => {
const name = customer.content?.match(/Customer Name : ([^|]+)/)?.[1]?.trim() || customer.name;
const phone = customer.content?.match(/Mobile Number : ([^|]+)/)?.[1]?.trim() || '';
return (
<button
key={customer.name}
type="button"
className={`w-full gap-2 px-4 py-2 text-left rounded-md text-gray-800 text-sm select-none transition-colors ${
idx === highlightedIndex ? 'bg-primary-50 text-primary-700' : 'hover:bg-gray-50'
}`}
onMouseDown={() => {
setSelectedCustomer({ id: customer.name, name, phone });
setSearchTerm('');
setIsOpen(false);
}}
onMouseEnter={() => setHighlightedIndex(idx)}
>
<div className="font-medium">{name}</div>
<div className="ml-auto text-xs text-gray-500">{phone}</div>
</button>
);
})}
{!isSearching && !searchError && searchResults.length === 0 && searchTerm.trim() && (
<div className="p-4 text-center text-gray-400 text-sm select-none">{t('customer.no_customers_found')}</div>
)}
<div className="my-1 h-px bg-gray-100" />
<button
type="button"
className={`flex items-center gap-2 w-full px-4 py-2 text-primary-600 hover:text-primary-700 hover:bg-gray-50 font-medium rounded-md text-sm select-none transition-colors ${
highlightedIndex === searchResults.length ? 'bg-primary-50' : ''
}`}
onMouseDown={() => {
// Prefill logic
if (/^\d+$/.test(searchTerm.trim())) {
setPrefillPhone(searchTerm.trim());
setPrefillName('');
} else {
setPrefillName(searchTerm.trim());
setPrefillPhone('');
}
setShowNewCustomerForm(true);
setIsOpen(false);
}}
onMouseEnter={() => setHighlightedIndex(searchResults.length)}
>
<UserPlus className="w-4 h-4" /> {searchTerm.trim() ? t('customer.add_with_name', { name: searchTerm.trim() }) : t('customer.add_new')}
</button>
</div>
)}
</div>
)}
{showNewCustomerForm && (
<Dialog
open={showNewCustomerForm}
onOpenChange={(open) => {
// Prevent closing the dialog when creating customer
if (!isCreatingCustomer) {
setShowNewCustomerForm(open);
}
}}
>
<DialogContent className="w-full max-w-md p-4 max-h-[80vh] overflow-y-auto">
<h3 className="text-lg font-semibold text-gray-900 mb-4">{t('customer.add_customer_title')}</h3>
<NewCustomerForm
onClose={() => setShowNewCustomerForm(false)}
isCreatingCustomer={isCreatingCustomer}
setIsCreatingCustomer={setIsCreatingCustomer}
prefillName={prefillName}
prefillPhone={prefillPhone}
/>
</DialogContent>
</Dialog>
)}
</div>
);
}
================================================
FILE: pos/src/components/Footer.tsx
================================================
import { NavLink } from 'react-router-dom';
import {
LayoutGrid,
ClipboardList,
Table,
} from 'lucide-react';
import { cn } from '../lib/utils';
import { t } from '../i18n';
const Footer = () => {
const navItems = [
{ icon: LayoutGrid, label: t('footer.pos'), path: '/' },
{icon: Table, label: t('footer.table'), path: '/table'},
{ icon: ClipboardList, label: t('footer.orders'), path: '/orders' },
];
return (
<div className="bg-white border-t border-gray-200 py-2 relative">
<nav className="max-w-screen-xl mx-auto px-4">
<div className="flex justify-center items-center gap-4">
{navItems.map((item) => (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
cn(
'flex flex-col items-center p-2 rounded-lg text-gray-700 hover:bg-gray-100 transition-colors',
isActive && 'text-blue-600'
)
}
>
<item.icon className="w-5 h-5" />
<span className="text-xs mt-1">{item.label}</span>
</NavLink>
))}
</div>
</nav>
</div>
);
};
export default Footer;
================================================
FILE: pos/src/components/Header.tsx
================================================
import { useState, useEffect, useRef } from 'react';
import { t } from '../i18n';
import { Link, useLocation } from 'react-router-dom';
import {
Command,
User,
ChevronDown,
Monitor,
LogOut,
RefreshCw,
} from 'lucide-react';
import { Button, Input } from './ui';
import { useRootStore } from '../store/root-store';
import { usePOSStore } from '../store/pos-store';
import type { RootState } from '../store/root-store';
import { logout } from '../lib/auth-api';
import { showToast } from './ui/toast';
const Header = () => {
const [showUserMenu, setShowUserMenu] = useState(false);
const userMenuRef = useRef<HTMLDivElement>(null);
const user = useRootStore((state: RootState) => state.user);
const searchInputRef = useRef<HTMLInputElement>(null);
const location = useLocation();
const { searchQuery, setSearchQuery } = usePOSStore();
const { orderSearchQuery, setOrderSearchQuery } = useRootStore();
const [orderSearchInput, setOrderSearchInput] = useState(orderSearchQuery);
// Determine placeholder and handlers based on route
let searchPlaceholder = t('header.search_placeholder_default');
let searchValue: string | undefined = undefined;
let searchOnChange: ((e: React.ChangeEvent<HTMLInputElement>) => void) | undefined = undefined;
if (location.pathname === '/orders') {
searchPlaceholder = t('header.search_placeholder_orders');
searchValue = orderSearchInput;
searchOnChange = (e) => setOrderSearchInput(e.target.value);
} else if (location.pathname === '/') {
searchPlaceholder = t('header.search_placeholder_menu');
searchValue = searchQuery;
searchOnChange = (e) => setSearchQuery(e.target.value);
}
// Debounce order search
useEffect(() => {
if (location.pathname !== '/orders') return;
const handler = setTimeout(() => {
setOrderSearchQuery(orderSearchInput);
}, 300);
return () => clearTimeout(handler);
}, [orderSearchInput, setOrderSearchQuery, location.pathname]);
// Keep input in sync with store (if cleared elsewhere)
useEffect(() => {
if (location.pathname === '/orders') {
setOrderSearchInput(orderSearchQuery);
}
}, [location.pathname, orderSearchQuery]);
// Handle clicks outside of menus
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (userMenuRef.current && !userMenuRef.current.contains(event.target as Node)) {
setShowUserMenu(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
e.preventDefault();
searchInputRef.current?.focus();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, []);
const handleUserMenuToggle = () => {
setShowUserMenu(!showUserMenu);
};
const handleLogout = async () => {
try {
await logout();
window.location.href = '/login?redirect-to=%2Fpos';
} catch (error) {
showToast.error(t('errors.failed_logout'));
}
};
const handleClearCache = () => {
// Clear all local storage
localStorage.clear();
// Clear all session storage
sessionStorage.clear();
// Reload the page
window.location.reload();
};
return (
<header className="bg-white border-b border-gray-200">
<div className="flex items-center justify-between h-16 px-6">
{/* Logo */}
<div className="flex items-center">
<Link to="/" className="flex items-center gap-3">
<img
src="/assets/ury/pos/ury_pos.png"
alt="URY POS"
className="h-10 w-auto"
/>
</Link>
</div>
{/* Search Bar */}
<div className="px-4 py-2 flex-1 flex items-center max-w-2xl mx-8 bg-gray-50 hover:bg-gray-100 border border-input rounded-md">
<Input
ref={searchInputRef}
placeholder={searchPlaceholder}
className="h-fit p-0 w-full bg-transparent border-0 focus:outline-none focus-visible:ring-0 focus-visible:ring-offset-0"
value={searchValue}
onChange={searchOnChange}
/>
<div className="flex items-center gap-2 text-gray-400">
<Command className="w-4 h-4" />
<span>K</span>
</div>
</div>
{/* Right side actions */}
<div className="flex items-center gap-4">
{/* User menu */}
<div className="relative" ref={userMenuRef}>
<Button
onClick={handleUserMenuToggle}
variant="ghost"
className="flex items-center gap-2 text-gray-600 hover:text-gray-900"
>
<div className="w-8 h-8 bg-primary-500 rounded-full flex items-center justify-center">
<User className="w-4 h-4 text-white" />
</div>
<span className="text-sm font-medium">{user?.full_name || 'User'}</span>
<ChevronDown className="w-4 h-4" />
</Button>
{/* User dropdown */}
{showUserMenu && (
<div className="absolute end-0 mt-2 w-56 bg-white rounded-lg shadow-lg border border-gray-200 z-50">
<div className="p-4 border-b border-gray-200">
<p className="text-sm font-medium text-gray-900">{user?.full_name || 'User'}</p>
<p className="text-sm text-gray-500">{user?.name || ''}</p>
</div>
<div className="py-2">
<Button
variant="ghost"
className="flex justify-start items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
onClick={() => window.location.href = '/app'}
>
<Monitor className="w-4 h-4 me-3" />
{t('header.switch_to_desk')}
</Button>
<Button
variant="ghost"
className="flex justify-start items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
onClick={handleClearCache}
>
<RefreshCw className="w-4 h-4 me-3" />
{t('header.clear_cache')}
</Button>
<Button
variant="ghost"
className="flex justify-start items-center w-full px-4 py-2 text-sm text-red-600 hover:bg-red-50 hover:text-red-700 transition-colors"
onClick={handleLogout}
>
<LogOut className="w-4 h-4 me-3" />
{t('header.logout')}
</Button>
</div>
</div>
)}
</div>
</div>
</div>
</header>
);
};
export default Header;
================================================
FILE: pos/src/components/InitialLoader.tsx
================================================
import React from 'react';
import { Spinner } from './ui/spinner';
import { t } from '../i18n';
const InitialLoader: React.FC = () => {
return (
<div className="fixed inset-0 bg-white flex items-center justify-center">
<div className="text-center">
<Spinner className="w-12 h-12" />
<p className="mt-4 text-lg font-medium text-gray-900">{t('common.loading_ury_pos')}</p>
<p className="mt-2 text-sm text-gray-500">{t('common.please_wait_setup')}</p>
</div>
</div>
);
};
export default InitialLoader;
================================================
FILE: pos/src/components/LayoutView.tsx
================================================
import React, { useState, useRef, useMemo, useEffect, useCallback } from 'react';
import { CreditCard as Edit3, Save, Users, Move, X, Grid3x3 as Grid3X3, ZoomIn, ZoomOut, RotateCcw } from 'lucide-react';
import { cn, formatInvoiceTime } from '../lib/utils';
import { Table, updateTableLayout } from '../lib/table-api';
import { getTableOrder, POSInvoice } from '../lib/order-api';
import { Button } from './ui';
import { t } from '../i18n';
interface Props {
selectedRoom: string;
tables: Table[];
onBackToGrid: () => void;
onRefresh?: () => void; // Add refresh callback
}
const LayoutView: React.FC<Props> = ({ selectedRoom, tables, onBackToGrid, onRefresh }) => {
const isRTL = document.dir === 'rtl';
const [isEditMode, setIsEditMode] = useState(false);
// Local state for optimistic updates
const [localLayouts, setLocalLayouts] = useState<Record<string, Partial<Table>>>({});
const [draggedTable, setDraggedTable] = useState<string | null>(null);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
const [selectedTable, setSelectedTable] = useState<string | null>(null);
const [selectedTableOrder, setSelectedTableOrder] = useState<POSInvoice | null>(null);
const canvasRef = useRef<HTMLDivElement>(null);
const [zoom, setZoom] = useState(1);
const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
const [isPanning, setIsPanning] = useState(false);
const [panStart, setPanStart] = useState({ x: 0, y: 0 });
const [capacityInput, setCapacityInput] = useState<string>('');
// Save to local storage effect removed
// Merge props.tables with saved positions
const tablesWithPosition = useMemo(() => {
return tables.map((table, index) => {
const local = localLayouts[table.name] || {};
// Use local overrides, then backend fields, then grid defaults
const x = local.layout_x ?? table.layout_x ?? (100 + (index % 5) * 150);
const y = local.layout_y ?? table.layout_y ?? (100 + Math.floor(index / 5) * 150);
return {
...table,
x,
y,
table_shape: local.table_shape ?? table.table_shape,
no_of_seats: local.no_of_seats ?? table.no_of_seats,
};
});
}, [tables, localLayouts]);
// Sync capacity input when selected table changes
useEffect(() => {
if (selectedTable) {
const table = tablesWithPosition.find(t => t.name === selectedTable);
setCapacityInput(table?.no_of_seats?.toString() ?? '');
}
}, [selectedTable, tablesWithPosition]);
// Calculate table dimensions based on capacity and shape
const getTableDimensions = (shape: string, capacity: number = 4) => {
// Dynamic sizing: minimum 60px, scales up by 10px per person, max 250px
const size = Math.max(60, Math.min(250, 60 + (capacity * 10)));
const normalizedShape = shape?.toLowerCase() || 'rectangle';
switch (normalizedShape) {
case 'circle':
return { width: size, height: size };
case 'square':
return { width: size, height: size };
case 'rectangle':
default:
return { width: size * 1.5, height: size };
}
};
// Zoom functionality (simple scale)
const handleZoomIn = () => setZoom(prev => Math.min(prev + 0.1, 3));
const handleZoomOut = () => setZoom(prev => Math.max(prev - 0.1, 0.3));
const handleResetZoom = () => {
setZoom(1);
setPanOffset({ x: 0, y: 0 });
};
// Mouse wheel zoom (simple scale)
const handleWheel = useCallback((e: WheelEvent) => {
e.preventDefault();
e.stopPropagation();
const delta = e.deltaY > 0 ? -0.1 : 0.1;
setZoom(prev => Math.max(0.3, Math.min(3, prev + delta)));
}, []);
// Use ref to attach non-passive listener for proper preventDefault
useEffect(() => {
const canvas = canvasRef.current;
if (canvas) {
canvas.addEventListener('wheel', handleWheel, { passive: false });
}
return () => {
if (canvas) {
canvas.removeEventListener('wheel', handleWheel);
}
};
}, [handleWheel]);
// Pan functionality
const handleCanvasMouseDown = (e: React.MouseEvent) => {
// Start panning if we clicked on the background (wrapper or outer container)
// Tables stop propagation, so if we get here, it's safe to pan
setIsPanning(true);
setPanStart({ x: e.clientX - panOffset.x, y: e.clientY - panOffset.y });
};
const handleCanvasMouseMove = (e: React.MouseEvent) => {
if (isPanning) {
setPanOffset({
x: e.clientX - panStart.x,
y: e.clientY - panStart.y
});
return;
}
// Drag functionality
if (!draggedTable || !isEditMode || !canvasRef.current) return;
const canvasRect = canvasRef.current.getBoundingClientRect();
// Classic drag math
const newX = (e.clientX - canvasRect.left) / zoom - dragOffset.x - panOffset.x / zoom;
const newY = (e.clientY - canvasRect.top) / zoom - dragOffset.y - panOffset.y / zoom;
// Update local state for immediate feedback
setLocalLayouts(prev => ({
...prev,
[draggedTable]: {
...(prev[draggedTable] || {}),
layout_x: newX,
layout_y: newY,
}
}));
};
const persistTableUpdate = (tableName: string, changes: Partial<Table>) => {
const table = tablesWithPosition.find(t => t.name === tableName);
if (!table) return Promise.reject("Table not found");
// AND ensure we fallback to backend values for undefined fields.
const payload = {
layout_x: changes.layout_x ?? table.x,
layout_y: changes.layout_y ?? table.y,
table_shape: changes.table_shape ?? table.table_shape,
no_of_seats: changes.no_of_seats ?? table.no_of_seats,
minimum_seating: table.minimum_seating // preserve existing if not changing
};
return updateTableLayout(tableName, payload);
};
const handleCanvasMouseUp = () => {
if (draggedTable && isEditMode) {
const table = tablesWithPosition.find(t => t.name === draggedTable);
if (table) {
// table.x and table.y are already updated via local state during drag
persistTableUpdate(table.name, {
layout_x: table.x,
layout_y: table.y
}).catch(err => console.error("Failed to save layout", err));
}
}
setIsPanning(false);
setDraggedTable(null);
setDragOffset({ x: 0, y: 0 });
};
const getTableStatusColor = (occupied: number) => {
return occupied
? 'bg-amber-100 border-amber-300 text-amber-900 shadow-sm'
: 'bg-emerald-50 border-emerald-200 text-emerald-800 hover:shadow-md';
};
const handleMouseDown = (e: React.MouseEvent, table: typeof tablesWithPosition[0]) => {
e.stopPropagation();
setSelectedTable(table.name);
if (table.occupied) {
getTableOrder(table.name).then(res => {
setSelectedTableOrder(res.message);
}).catch(console.error);
} else {
setSelectedTableOrder(null);
}
if (!isEditMode) return;
const canvasRect = canvasRef.current?.getBoundingClientRect();
if (!canvasRect) return;
// Calculate offset for drag start
// We need to match the drag math:
// newX = (mouseX - rect.left)/zoom - dragOffset - pan/zoom
// So dragOffset = (mouseX - rect)/zoom - startX - pan/zoom
// Actually, just use standard offset from top-left of element?
// Wait, the newX formula sets the new TopLeft.
// So dragOffset should be the difference between Mouse and TableTopLeft (in scaled/world units?)
// User formula: newX = (mouseX - canvasRect.left) / zoom - dragOffset.x - panOffset.x / zoom
// So when we start drag:
// table.x = (mouseX - rect.left)/zoom - dragOffset.x - panOffset.x/zoom
// dragOffset.x = (mouseX - rect.left)/zoom - table.x - panOffset.x/zoom
const mouseX = e.clientX;
const mouseY = e.clientY;
setDraggedTable(table.name);
setDragOffset({
x: (mouseX - canvasRect.left) / zoom - table.x - panOffset.x / zoom,
y: (mouseY - canvasRect.top) / zoom - table.y - panOffset.y / zoom
});
};
const TableShape = ({ table }: { table: typeof tablesWithPosition[0] }) => {
const dimensions = getTableDimensions(table.table_shape, table.no_of_seats);
const baseClasses = cn(
'absolute border-2 flex items-center justify-center text-sm font-semibold cursor-pointer transition-all select-none',
getTableStatusColor(table.occupied),
isEditMode && 'hover:ring-2 hover:ring-blue-400 cursor-move',
draggedTable === table.name && 'shadow-xl scale-105 z-20',
selectedTable === table.name && 'ring-2 ring-blue-600 z-10'
);
const style = {
left: table.x,
top: table.y,
width: dimensions.width,
height: dimensions.height,
transform: `scale(${zoom})`,
transformOrigin: 'top left',
};
const shapeLower = table.table_shape?.toLowerCase();
const shapeClasses = {
circle: 'rounded-full',
square: 'rounded-lg',
rectangle: 'rounded-md'
};
const roundedClass = shapeClasses[shapeLower as keyof typeof shapeClasses] || shapeClasses.rectangle;
return (
<div
className={cn(baseClasses, roundedClass)}
style={style}
onMouseDown={(e) => handleMouseDown(e, table)}
>
<div className="text-center p-1 overflow-hidden pointer-events-none">
<div className="font-bold truncate px-1">{table.name}</div>
<div className="text-[10px] flex items-center justify-center gap-1 opacity-80">
<Users className="w-3 h-3" />
{table.no_of_seats || '-'}
</div>
</div>
{isEditMode && (
<>
<div className="absolute -top-1 -right-1 bg-blue-500 text-white rounded-full p-0.5 shadow-sm">
<Move className="w-2 h-2" />
</div>
</>
)}
</div>
);
};
// Helper to format invoice time (consistent with Table.tsx) removed - imported from utils
const handleCapacityChange = (capacityStr: string) => {
if (!selectedTable) return;
// Always update the input field value to allow free typing
setCapacityInput(capacityStr);
const capacity = parseInt(capacityStr);
if (isNaN(capacity) || capacity < 1 || capacity > 20) return;
const currentTable = tablesWithPosition.find(t => t.name === selectedTable);
if (!currentTable) return;
setLocalLayouts(prev => ({
...prev,
[selectedTable]: {
...(prev[selectedTable] || {}),
no_of_seats: capacity
}
}));
updateTableLayout(selectedTable, { no_of_seats: capacity })
.catch(console.error);
}
const handleDropdownShapeChange = (shape: string) => {
if (!selectedTable) return;
const currentTable = tablesWithPosition.find(t => t.name === selectedTable);
if (!currentTable) return;
setLocalLayouts(prev => ({
...prev,
[selectedTable]: {
...(prev[selectedTable] || {}),
table_shape: shape as any
}
}));
updateTableLayout(selectedTable, { table_shape: shape as any })
.catch(console.error);
}
const selectedTableData = tablesWithPosition.find(t => t.name === selectedTable);
return (
<div className="flex flex-col h-full bg-gray-50">
{/* Header Controls */}
<div className="bg-white border-b border-gray-200 p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Button
onClick={onBackToGrid}
variant="outline"
className="flex items-center gap-2"
>
<Grid3X3 className="w-4 h-4" />
{t('tables.grid_view')}
</Button>
<h2 className="text-lg font-semibold">{selectedRoom} <span className="text-gray-400 mx-2">|</span> {t('tables.layout')}</h2>
</div>
<div className="flex items-center gap-2">
{/* Edit Mode Toggle */}
<div className="">
<button
onClick={() => {
if (isEditMode) {
onRefresh?.();
}
setIsEditMode(!isEditMode);
}}
className={cn(
'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium border transition-all',
isEditMode
? 'bg-blue-600 hover:bg-blue-700 text-white border-green-700'
: 'bg-white hover:bg-gray-50 text-gray-700 border-gray-200'
)}
>
{isEditMode ? <Save className="w-4 h-4" /> : <Edit3 className="w-4 h-4" />}
{i
gitextract_j3w6bb6w/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── AGENTS.MD
├── CLAUDE.MD
├── FEATURES.md
├── INSTALLATION.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── SETUP.md
├── TERMS.md
├── URYMosaic/
│ ├── .gitignore
│ ├── .vscode/
│ │ └── extensions.json
│ ├── AGENTS.MD
│ ├── CLAUDE.MD
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── proxyOptions.js
│ ├── src/
│ │ ├── App.vue
│ │ ├── components/
│ │ │ ├── Header.vue
│ │ │ └── kot.vue
│ │ ├── index.css
│ │ ├── main.js
│ │ ├── router/
│ │ │ ├── auth.js
│ │ │ └── index.js
│ │ ├── style.css
│ │ └── views/
│ │ ├── Home.vue
│ │ └── Login.vue
│ ├── tailwind.config.js
│ └── vite.config.js
├── package.json
├── pos/
│ ├── .gitignore
│ ├── AGENTS.MD
│ ├── CLAUDE.MD
│ ├── README.md
│ ├── eslint.config.js
│ ├── index.html
│ ├── package.json
│ ├── postcss.config.js
│ ├── privateKey.js
│ ├── src/
│ │ ├── App.tsx
│ │ ├── components/
│ │ │ ├── AggregatorSelect.tsx
│ │ │ ├── AuthGuard.tsx
│ │ │ ├── CommentDialog.tsx
│ │ │ ├── CustomerSelect.tsx
│ │ │ ├── Footer.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── InitialLoader.tsx
│ │ │ ├── LayoutView.tsx
│ │ │ ├── MenuCard.tsx
│ │ │ ├── MenuList.tsx
│ │ │ ├── OrderPanel.tsx
│ │ │ ├── OrderStatusSidebar.tsx
│ │ │ ├── OrderTypeSelect.tsx
│ │ │ ├── POSOpeningDialog.tsx
│ │ │ ├── POSOpeningProvider.tsx
│ │ │ ├── PaymentDialog.tsx
│ │ │ ├── ProductDialog.tsx
│ │ │ ├── ScreenSizeDialog.tsx
│ │ │ ├── ScreenSizeProvider.tsx
│ │ │ ├── SearchBar.tsx
│ │ │ ├── Sidebar.tsx
│ │ │ ├── Spotlight.tsx
│ │ │ ├── TableSelectionDialog.tsx
│ │ │ ├── TableShapeIcon.tsx
│ │ │ └── ui/
│ │ │ ├── README.md
│ │ │ ├── badge.tsx
│ │ │ ├── button.tsx
│ │ │ ├── card.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── example.tsx
│ │ │ ├── index.ts
│ │ │ ├── input.tsx
│ │ │ ├── loader.tsx
│ │ │ ├── select.tsx
│ │ │ ├── spinner.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── toast.css
│ │ │ └── toast.tsx
│ │ ├── data/
│ │ │ ├── doctypes.ts
│ │ │ ├── menu-data.ts
│ │ │ └── order-types.ts
│ │ ├── i18n/
│ │ │ ├── config.ts
│ │ │ ├── index.ts
│ │ │ ├── loader.ts
│ │ │ ├── locales/
│ │ │ │ ├── ar.json
│ │ │ │ ├── en.json
│ │ │ │ └── fr.json
│ │ │ └── resolve-language.ts
│ │ ├── index.css
│ │ ├── lib/
│ │ │ ├── aggregator-api.ts
│ │ │ ├── auth-api.ts
│ │ │ ├── customer-api.ts
│ │ │ ├── frappe-sdk.ts
│ │ │ ├── invoice-api.ts
│ │ │ ├── menu-api.ts
│ │ │ ├── menu-course-api.ts
│ │ │ ├── order-api.ts
│ │ │ ├── payment-api.ts
│ │ │ ├── pos-opening-api.ts
│ │ │ ├── pos-profile-api.ts
│ │ │ ├── print-qz.ts
│ │ │ ├── print.ts
│ │ │ ├── role-utils.ts
│ │ │ ├── storage.ts
│ │ │ ├── table-api.ts
│ │ │ └── utils.ts
│ │ ├── main.tsx
│ │ ├── pages/
│ │ │ ├── Orders.tsx
│ │ │ ├── POS.tsx
│ │ │ └── Table.tsx
│ │ ├── store/
│ │ │ ├── pos-store.ts
│ │ │ ├── root-store.ts
│ │ │ └── slices/
│ │ │ ├── auth-slice.ts
│ │ │ ├── config-slice.ts
│ │ │ └── orders-slice.ts
│ │ └── vite-env.d.ts
│ ├── tailwind.config.js
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── pyproject.toml
├── requirements.txt
├── setup.py
├── ury/
│ ├── __init__.py
│ ├── config/
│ │ ├── __init__.py
│ │ ├── desktop.py
│ │ └── docs.py
│ ├── fixtures/
│ │ ├── client_script.json
│ │ ├── custom_field.json
│ │ ├── custom_html_block.json
│ │ ├── property_setter.json
│ │ └── role.json
│ ├── hooks.py
│ ├── install.py
│ ├── modules.txt
│ ├── patches/
│ │ └── v2_0/
│ │ └── default_permissions.py
│ ├── patches.txt
│ ├── permission.py
│ ├── public/
│ │ ├── .gitkeep
│ │ ├── images
│ │ └── js/
│ │ ├── jsrsasign-all-min.js
│ │ ├── pos_extend.js
│ │ ├── pos_print.js
│ │ ├── quick_entry.js
│ │ ├── qz-tray.js
│ │ ├── restrict_qty_edit_pos.js
│ │ ├── sign-message.js
│ │ └── ury_pos_kot.js
│ ├── setup.py
│ ├── templates/
│ │ ├── __init__.py
│ │ └── pages/
│ │ └── __init__.py
│ ├── uninstall.py
│ ├── ury/
│ │ ├── __init__.py
│ │ ├── api/
│ │ │ ├── button_permission.py
│ │ │ ├── pos_extend.py
│ │ │ ├── ury_kot_display.py
│ │ │ ├── ury_kot_generate.py
│ │ │ ├── ury_kot_notification.py
│ │ │ ├── ury_kot_order_number.py
│ │ │ ├── ury_kot_reprint.py
│ │ │ ├── ury_kot_validation.py
│ │ │ ├── ury_menu_course_validation.py
│ │ │ └── ury_print.py
│ │ ├── custom/
│ │ │ └── item.json
│ │ ├── doctype/
│ │ │ ├── __init__.py
│ │ │ ├── aggregator_settings/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── aggregator_settings.json
│ │ │ │ └── aggregator_settings.py
│ │ │ ├── item_add_on/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── item_add_on.json
│ │ │ │ └── item_add_on.py
│ │ │ ├── menu_for_room/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── menu_for_room.js
│ │ │ │ ├── menu_for_room.json
│ │ │ │ ├── menu_for_room.py
│ │ │ │ └── test_menu_for_room.py
│ │ │ ├── multiple_rooms/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── multiple_rooms.json
│ │ │ │ └── multiple_rooms.py
│ │ │ ├── order_type_menu/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── order_type_menu.json
│ │ │ │ └── order_type_menu.py
│ │ │ ├── pos_item_variants/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── pos_item_variants.json
│ │ │ │ └── pos_item_variants.py
│ │ │ ├── role_permitted/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── role_permitted.json
│ │ │ │ └── role_permitted.py
│ │ │ ├── sub_pos_closing/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── sub_pos_closing.js
│ │ │ │ ├── sub_pos_closing.json
│ │ │ │ ├── sub_pos_closing.py
│ │ │ │ └── test_sub_pos_closing.py
│ │ │ ├── sub_pos_closing_payment/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── sub_pos_closing_payment.json
│ │ │ │ └── sub_pos_closing_payment.py
│ │ │ ├── sub_pos_invoices/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── sub_pos_invoices.json
│ │ │ │ └── sub_pos_invoices.py
│ │ │ ├── ury_cost_of_goods/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_cost_of_goods.json
│ │ │ │ └── ury_cost_of_goods.py
│ │ │ ├── ury_daily_p_and_l/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── profit_loss_details.html
│ │ │ │ ├── test_ury_daily_p_and_l.py
│ │ │ │ ├── ury_daily_p_and_l.js
│ │ │ │ ├── ury_daily_p_and_l.json
│ │ │ │ └── ury_daily_p_and_l.py
│ │ │ ├── ury_fixed_expenses/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_fixed_expenses.json
│ │ │ │ └── ury_fixed_expenses.py
│ │ │ ├── ury_kot/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_kot.py
│ │ │ │ ├── ury_kot.js
│ │ │ │ ├── ury_kot.json
│ │ │ │ └── ury_kot.py
│ │ │ ├── ury_kot_error_log/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_kot_error_log.py
│ │ │ │ ├── ury_kot_error_log.js
│ │ │ │ ├── ury_kot_error_log.json
│ │ │ │ └── ury_kot_error_log.py
│ │ │ ├── ury_kot_items/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_kot_items.json
│ │ │ │ └── ury_kot_items.py
│ │ │ ├── ury_materials/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_materials.json
│ │ │ │ └── ury_materials.py
│ │ │ ├── ury_menu/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_menu.py
│ │ │ │ ├── ury_menu.js
│ │ │ │ ├── ury_menu.json
│ │ │ │ └── ury_menu.py
│ │ │ ├── ury_menu_course/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_menu_course.py
│ │ │ │ ├── ury_menu_course.js
│ │ │ │ ├── ury_menu_course.json
│ │ │ │ └── ury_menu_course.py
│ │ │ ├── ury_menu_item/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_menu_item.json
│ │ │ │ └── ury_menu_item.py
│ │ │ ├── ury_notification_recipient/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_notification_recipient.py
│ │ │ │ ├── ury_notification_recipient.js
│ │ │ │ ├── ury_notification_recipient.json
│ │ │ │ └── ury_notification_recipient.py
│ │ │ ├── ury_order/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_order.py
│ │ │ │ ├── ury_order.js
│ │ │ │ ├── ury_order.json
│ │ │ │ └── ury_order.py
│ │ │ ├── ury_order_item/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_order_item.py
│ │ │ │ ├── ury_order_item.js
│ │ │ │ ├── ury_order_item.json
│ │ │ │ └── ury_order_item.py
│ │ │ ├── ury_p_and_l_breakup/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_p_and_l_breakup.json
│ │ │ │ └── ury_p_and_l_breakup.py
│ │ │ ├── ury_p_and_l_materials/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_p_and_l_materials.json
│ │ │ │ └── ury_p_and_l_materials.py
│ │ │ ├── ury_printer_settings/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_printer_settings.json
│ │ │ │ └── ury_printer_settings.py
│ │ │ ├── ury_production_item_groups/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_production_item_groups.json
│ │ │ │ └── ury_production_item_groups.py
│ │ │ ├── ury_production_unit/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_production_unit.py
│ │ │ │ ├── ury_production_unit.js
│ │ │ │ ├── ury_production_unit.json
│ │ │ │ └── ury_production_unit.py
│ │ │ ├── ury_report_settings/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_report_settings.py
│ │ │ │ ├── ury_report_settings.js
│ │ │ │ ├── ury_report_settings.json
│ │ │ │ └── ury_report_settings.py
│ │ │ ├── ury_restaurant/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_restaurant.py
│ │ │ │ ├── ury_restaurant.js
│ │ │ │ ├── ury_restaurant.json
│ │ │ │ └── ury_restaurant.py
│ │ │ ├── ury_room/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_room.py
│ │ │ │ ├── ury_room.js
│ │ │ │ ├── ury_room.json
│ │ │ │ └── ury_room.py
│ │ │ ├── ury_table/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_ury_table.py
│ │ │ │ ├── ury_table.js
│ │ │ │ ├── ury_table.json
│ │ │ │ └── ury_table.py
│ │ │ ├── ury_user/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ury_user.json
│ │ │ │ └── ury_user.py
│ │ │ └── ury_variable_expenses/
│ │ │ ├── __init__.py
│ │ │ ├── ury_variable_expenses.json
│ │ │ └── ury_variable_expenses.py
│ │ ├── hooks/
│ │ │ ├── ury_item.py
│ │ │ ├── ury_pos_closing_entry.py
│ │ │ ├── ury_pos_invoice.py
│ │ │ ├── ury_pos_opening_entry.py
│ │ │ ├── ury_pos_profile.py
│ │ │ └── ury_sales_invoice.py
│ │ ├── page/
│ │ │ ├── __init__.py
│ │ │ └── websocket_print/
│ │ │ ├── __init__.py
│ │ │ ├── websocket_print.js
│ │ │ └── websocket_print.json
│ │ ├── report/
│ │ │ ├── __init__.py
│ │ │ ├── average_bill_value/
│ │ │ │ ├── __init__.py
│ │ │ │ └── average_bill_value.json
│ │ │ ├── cancelled_invoices/
│ │ │ │ ├── __init__.py
│ │ │ │ └── cancelled_invoices.json
│ │ │ ├── customer_data/
│ │ │ │ ├── __init__.py
│ │ │ │ └── customer_data.json
│ │ │ ├── daywise_customer_details/
│ │ │ │ ├── __init__.py
│ │ │ │ └── daywise_customer_details.json
│ │ │ ├── daywise_invoices/
│ │ │ │ ├── __init__.py
│ │ │ │ └── daywise_invoices.json
│ │ │ ├── daywise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── daywise_sales.json
│ │ │ ├── employee_item_wise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── employee_item_wise_sales.json
│ │ │ ├── employee_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── employee_sales.json
│ │ │ ├── item_wise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── item_wise_sales.json
│ │ │ ├── month_wise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── month_wise_sales.json
│ │ │ ├── repeated_customers/
│ │ │ │ ├── __init__.py
│ │ │ │ └── repeated_customers.json
│ │ │ ├── service_wise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── service_wise_sales.json
│ │ │ ├── time_wise_sales/
│ │ │ │ ├── __init__.py
│ │ │ │ └── time_wise_sales.json
│ │ │ └── today's_sales/
│ │ │ ├── __init__.py
│ │ │ └── today's_sales.json
│ │ └── workspace/
│ │ └── ury/
│ │ └── ury.json
│ ├── ury_pos/
│ │ └── api.py
│ └── www/
│ ├── __init__.py
│ └── pos.py
└── urypos/
├── .gitignore
├── .vscode/
│ └── extensions.json
├── README.md
├── index.html
├── package.json
├── postcss.config.js
├── privateKey.js
├── proxyOptions.js
├── src/
│ ├── App.vue
│ ├── components/
│ │ ├── Cart.vue
│ │ ├── Customer.vue
│ │ ├── Header.vue
│ │ ├── Login.vue
│ │ ├── Menu.vue
│ │ ├── NotificationModal.vue
│ │ ├── Search.vue
│ │ ├── Table.vue
│ │ ├── bottomTabs.vue
│ │ ├── orderInfo.vue
│ │ ├── posClosing.vue
│ │ ├── posOpening.vue
│ │ ├── recentOrder.vue
│ │ └── takeAwayTable.vue
│ ├── index.css
│ ├── main.js
│ ├── router/
│ │ ├── auth.js
│ │ └── index.js
│ ├── stores/
│ │ ├── Alert.js
│ │ ├── Auth.js
│ │ ├── Customer.js
│ │ ├── Menu.js
│ │ ├── Notification.js
│ │ ├── NotificationModal.js
│ │ ├── Table.js
│ │ ├── bottomTabs.js
│ │ ├── frappeSdk.js
│ │ ├── invoiceData.js
│ │ ├── posClosing.js
│ │ ├── posOpening.js
│ │ ├── recentOrder.js
│ │ └── utils/
│ │ └── PrintWithQz.js
│ ├── style.css
│ └── views/
│ ├── Home.vue
│ └── Login.vue
├── tailwind.config.js
└── vite.config.js
SYMBOL INDEX (769 symbols across 152 files)
FILE: pos/src/App.tsx
function App (line 15) | function App() {
FILE: pos/src/components/AggregatorSelect.tsx
type AggregatorSelectProps (line 6) | interface AggregatorSelectProps {
function AggregatorSelect (line 10) | function AggregatorSelect({ disabled }: AggregatorSelectProps) {
FILE: pos/src/components/AuthGuard.tsx
type Props (line 7) | interface Props {
FILE: pos/src/components/CommentDialog.tsx
type CommentDialogProps (line 6) | interface CommentDialogProps {
FILE: pos/src/components/CustomerSelect.tsx
function NewCustomerForm (line 13) | function NewCustomerForm({
type CustomerSelectProps (line 221) | interface CustomerSelectProps {
function CustomerSelect (line 225) | function CustomerSelect({ disabled }: CustomerSelectProps) {
FILE: pos/src/components/LayoutView.tsx
type Props (line 11) | interface Props {
FILE: pos/src/components/MenuCard.tsx
type MenuCardProps (line 4) | interface MenuCardProps {
FILE: pos/src/components/MenuList.tsx
type MenuListProps (line 8) | interface MenuListProps {
FILE: pos/src/components/OrderStatusSidebar.tsx
type OrderStatusSidebarProps (line 8) | interface OrderStatusSidebarProps {
FILE: pos/src/components/OrderTypeSelect.tsx
type OrderTypeSelectProps (line 12) | interface OrderTypeSelectProps {
FILE: pos/src/components/POSOpeningDialog.tsx
type POSOpeningDialogProps (line 5) | interface POSOpeningDialogProps {
FILE: pos/src/components/POSOpeningProvider.tsx
type POSOpeningProviderProps (line 7) | interface POSOpeningProviderProps {
type ValidationType (line 11) | type ValidationType = 'opening' | 'closing' | null;
FILE: pos/src/components/PaymentDialog.tsx
type PaymentDialogProps (line 11) | interface PaymentDialogProps {
FILE: pos/src/components/ProductDialog.tsx
type Variant (line 9) | interface Variant {
type Addon (line 15) | interface Addon {
type ProductDialogProps (line 22) | interface ProductDialogProps {
FILE: pos/src/components/ScreenSizeProvider.tsx
type ScreenSizeProviderProps (line 4) | interface ScreenSizeProviderProps {
FILE: pos/src/components/SearchBar.tsx
type SearchBarProps (line 5) | interface SearchBarProps {
function SearchBar (line 13) | function SearchBar({
FILE: pos/src/components/Sidebar.tsx
type SidebarProps (line 12) | interface SidebarProps {
FILE: pos/src/components/TableSelectionDialog.tsx
type Props (line 13) | interface Props {
function fetchRooms (line 33) | async function fetchRooms() {
function fetchTables (line 70) | async function fetchTables() {
FILE: pos/src/components/TableShapeIcon.tsx
type TableShapeIconProps (line 4) | interface TableShapeIconProps {
FILE: pos/src/components/ui/badge.tsx
type BadgeProps (line 38) | interface BadgeProps
function Badge (line 42) | function Badge({ className, variant, size, ...props }: BadgeProps) {
FILE: pos/src/components/ui/button.tsx
type ButtonProps (line 39) | interface ButtonProps
FILE: pos/src/components/ui/card.tsx
type CardProps (line 30) | interface CardProps
FILE: pos/src/components/ui/dialog.tsx
type DialogProps (line 66) | interface DialogProps
type DialogContentProps (line 94) | interface DialogContentProps
FILE: pos/src/components/ui/input.tsx
type InputProps (line 28) | interface InputProps
FILE: pos/src/components/ui/select.tsx
type SelectProps (line 29) | interface SelectProps extends Omit<React.ComponentPropsWithoutRef<typeof...
FILE: pos/src/components/ui/spinner.tsx
type SpinnerProps (line 4) | interface SpinnerProps {
function Spinner (line 10) | function Spinner({ className, message, hideMessage = false}: SpinnerProp...
FILE: pos/src/components/ui/textarea.tsx
type TextareaProps (line 4) | interface TextareaProps
FILE: pos/src/data/doctypes.ts
constant DOCTYPES (line 1) | const DOCTYPES={
FILE: pos/src/data/order-types.ts
type OrderType (line 3) | type OrderType = "Dine In" | "Take Away" | "Delivery" | "Phone In" | "Ag...
type OrderTypes (line 5) | type OrderTypes= {
constant ORDER_TYPES (line 11) | const ORDER_TYPES: OrderTypes[] = [
constant DINE_IN (line 39) | const DINE_IN="Dine In"
constant DEFAULT_ORDER_TYPE (line 40) | const DEFAULT_ORDER_TYPE="Take Away"
constant DEFAULT_PAYMENT_MODE (line 41) | const DEFAULT_PAYMENT_MODE="Cash"
type OrderStatusType (line 43) | type OrderStatusType = "Draft" | "Unbilled" | "Recently Paid" | "Paid" |...
constant BASE_ORDER_STATUS_TYPES (line 46) | const BASE_ORDER_STATUS_TYPES = [
constant RECENTLY_PAID_STATUS_TYPE (line 58) | const RECENTLY_PAID_STATUS_TYPE = [
constant EXTENDED_ORDER_STATUS_TYPES (line 66) | const EXTENDED_ORDER_STATUS_TYPES = [
constant ORDER_STATUS_TYPES (line 99) | const ORDER_STATUS_TYPES = BASE_ORDER_STATUS_TYPES;
FILE: pos/src/i18n/config.ts
constant DEFAULT_LANGUAGE (line 1) | const DEFAULT_LANGUAGE = 'en';
constant SUPPORTED_LANGUAGES (line 3) | const SUPPORTED_LANGUAGES: Record<string, string> = {
FILE: pos/src/i18n/index.ts
type TranslationMap (line 5) | type TranslationMap = Record<string, unknown>;
function initI18n (line 13) | async function initI18n(lang?: string): Promise<void> {
function getActiveDirection (line 23) | function getActiveDirection(): 'ltr' | 'rtl' {
function applyDocumentLocale (line 35) | function applyDocumentLocale(): void {
function getActiveLanguage (line 44) | function getActiveLanguage(): string {
function t (line 55) | function t(key: string, params?: Record<string, string>): string {
FILE: pos/src/i18n/loader.ts
type TranslationMap (line 3) | type TranslationMap = Record<string, unknown>;
function loadLocale (line 7) | async function loadLocale(lang: string): Promise<TranslationMap> {
FILE: pos/src/i18n/resolve-language.ts
function resolveLanguage (line 9) | function resolveLanguage(): string {
FILE: pos/src/lib/aggregator-api.ts
type Aggregator (line 3) | interface Aggregator {
type GetAggregatorsResponse (line 7) | interface GetAggregatorsResponse {
function getAggregators (line 11) | async function getAggregators(): Promise<Aggregator[]> {
FILE: pos/src/lib/auth-api.ts
type LoggedUserResponse (line 3) | type LoggedUserResponse = string | null;
type UserDoc (line 5) | interface UserDoc {
FILE: pos/src/lib/customer-api.ts
type Customer (line 4) | interface Customer {
type CreateCustomerData (line 33) | interface CreateCustomerData {
type CreateCustomerResponse (line 40) | interface CreateCustomerResponse {
function getCustomerGroups (line 46) | async function getCustomerGroups() {
function getCustomerTerritories (line 58) | async function getCustomerTerritories() {
function addCustomer (line 70) | async function addCustomer(
function getscramblePattern (line 94) | function getscramblePattern(text: string) {
function searchCustomers (line 98) | async function searchCustomers(search: string, limit = 5) {
FILE: pos/src/lib/invoice-api.ts
type POSInvoice (line 4) | interface POSInvoice {
type POSInvoiceItem (line 22) | interface POSInvoiceItem {
type POSInvoiceTax (line 28) | interface POSInvoiceTax {
type GetPOSInvoicesResponse (line 33) | interface GetPOSInvoicesResponse {
type GetPOSInvoicesParams (line 40) | interface GetPOSInvoicesParams {
type GetPOSInvoiceItemsResponse (line 47) | interface GetPOSInvoiceItemsResponse {
function getPOSInvoices (line 51) | async function getPOSInvoices({
function getPOSInvoiceItems (line 80) | async function getPOSInvoiceItems(invoiceId: string) {
function updateInvoiceStatus (line 99) | async function updateInvoiceStatus(
function searchPosInvoice (line 114) | async function searchPosInvoice(query: string, status: string) {
function getInvoicePrintHtml (line 127) | async function getInvoicePrintHtml(invoiceId: string, printFormat: strin...
function networkPrint (line 148) | async function networkPrint(orderId: string, printer: string, printForma...
function selectNetworkPrinter (line 157) | async function selectNetworkPrinter(orderId: string, posProfile: string,...
function updatePrintStatus (line 166) | async function updatePrintStatus(orderId: string) {
FILE: pos/src/lib/menu-api.ts
type MenuItem (line 3) | interface MenuItem {
type GetMenuResponse (line 17) | interface GetMenuResponse {
type GetAggregatorMenuResponse (line 23) | interface GetAggregatorMenuResponse {
FILE: pos/src/lib/menu-course-api.ts
type MenuCourse (line 3) | interface MenuCourse {
type MenuCourseResponse (line 8) | interface MenuCourseResponse {
function getMenuCourses (line 13) | async function getMenuCourses(): Promise<MenuCourse[]> {
FILE: pos/src/lib/order-api.ts
type POSInvoiceItem (line 3) | interface POSInvoiceItem {
type POSInvoice (line 18) | interface POSInvoice {
type TableOrder (line 37) | interface TableOrder {
function getTableOrder (line 46) | async function getTableOrder(table_no: string): Promise<TableOrder> {
type SyncOrderRequest (line 59) | interface SyncOrderRequest {
FILE: pos/src/lib/payment-api.ts
type PaymentMode (line 3) | interface PaymentMode {
type PaymentModeResponse (line 8) | interface PaymentModeResponse {
FILE: pos/src/lib/pos-opening-api.ts
type POSOpeningResponse (line 3) | interface POSOpeningResponse {
type POSCloseValidationResponse (line 7) | interface POSCloseValidationResponse {
FILE: pos/src/lib/pos-profile-api.ts
type PosProfileLimited (line 5) | interface PosProfileLimited {
type PosProfileLimitedResponse (line 26) | interface PosProfileLimitedResponse {
type RolePermission (line 30) | interface RolePermission {
type PosProfileFull (line 46) | interface PosProfileFull {
type PosProfileCombined (line 70) | interface PosProfileCombined extends PosProfileFull {
type Currency (line 89) | interface Currency {
type PosProfileFullResponse (line 98) | interface PosProfileFullResponse {
function getPosProfileLimitedFields (line 102) | async function getPosProfileLimitedFields(): Promise<PosProfileLimited> {
function getPosProfileFull (line 107) | async function getPosProfileFull(posProfileName: string): Promise<PosPro...
function getCombinedPosProfile (line 112) | async function getCombinedPosProfile(): Promise<PosProfileCombined> {
function getCurrencyInfo (line 141) | async function getCurrencyInfo(currencyCode: string): Promise<Currency> {
FILE: pos/src/lib/print-qz.ts
function loadQzPrinter (line 6) | async function loadQzPrinter(host: string): Promise<void> {
function disconnectQzPrinter (line 17) | function disconnectQzPrinter(): void {
function printWithQz (line 21) | async function printWithQz(host: string, htmlToPrint: string): Promise<v...
FILE: pos/src/lib/print.ts
type PrintOrderParams (line 10) | interface PrintOrderParams {
function printOrder (line 15) | async function printOrder({ orderId, posProfile }: PrintOrderParams): Pr...
FILE: pos/src/lib/table-api.ts
type Room (line 4) | interface Room {
type Table (line 9) | interface Table {
function getRestaurantMenu (line 23) | async function getRestaurantMenu(posProfile: string, room?: string | nul...
function getRooms (line 33) | async function getRooms(branch: string): Promise<Room[]> {
function getTableCount (line 43) | async function getTableCount(room: string, branch?: string): Promise<num...
function getTables (line 57) | async function getTables(room: string): Promise<Table[]> {
function updateTableLayout (line 79) | async function updateTableLayout(name: string, data: Partial<Table>) {
FILE: pos/src/lib/utils.ts
function cn (line 5) | function cn(...inputs: ClassValue[]) {
function formatCurrency (line 9) | function formatCurrency(amount: number): string {
FILE: pos/src/pages/Orders.tsx
function Orders (line 18) | function Orders() {
FILE: pos/src/pages/POS.tsx
function POS (line 14) | function POS() {
FILE: pos/src/pages/Table.tsx
function fetchRooms (line 43) | async function fetchRooms() {
function fetchRoomCounts (line 94) | async function fetchRoomCounts() {
FILE: pos/src/store/pos-store.ts
constant MAX_QUANTITY (line 13) | const MAX_QUANTITY = 99;
constant MIN_QUANTITY (line 14) | const MIN_QUANTITY = 0;
constant ITEMS_PER_PAGE (line 15) | const ITEMS_PER_PAGE = 10;
class CartError (line 18) | class CartError extends Error {
method constructor (line 19) | constructor(message: string) {
type MenuItem (line 26) | interface MenuItem extends Omit<APIMenuItem, 'rate' | 'item_image'> {
type Customer (line 42) | interface Customer {
type OrderItem (line 48) | interface OrderItem extends MenuItem {
type PaymentMode (line 56) | interface PaymentMode {
type Category (line 62) | interface Category {
type Order (line 67) | interface Order {
type CartTotals (line 81) | interface CartTotals {
type Aggregator (line 88) | interface Aggregator {
type POSState (line 92) | interface POSState {
type POSStore (line 125) | interface POSStore extends POSState {
FILE: pos/src/store/root-store.ts
type RootState (line 6) | type RootState = AuthSlice & ConfigSlice & OrdersSlice;
FILE: pos/src/store/slices/auth-slice.ts
type User (line 4) | interface User {
type AuthState (line 10) | interface AuthState {
type AuthActions (line 16) | interface AuthActions {
type AuthSlice (line 22) | type AuthSlice = AuthState & AuthActions;
FILE: pos/src/store/slices/config-slice.ts
type RolePermission (line 5) | interface RolePermission {
type ConfigState (line 20) | interface ConfigState {
type ConfigActions (line 28) | interface ConfigActions {
type ConfigSlice (line 34) | type ConfigSlice = ConfigState & ConfigActions;
FILE: pos/src/store/slices/orders-slice.ts
type POSInvoice (line 7) | interface POSInvoice {
type OrdersState (line 25) | interface OrdersState {
type OrdersActions (line 43) | interface OrdersActions {
type OrdersSlice (line 54) | type OrdersSlice = OrdersState & OrdersActions;
constant ITEMS_PER_PAGE (line 56) | const ITEMS_PER_PAGE = 10;
FILE: ury/config/desktop.py
function get_data (line 3) | def get_data():
FILE: ury/config/docs.py
function get_context (line 9) | def get_context(context):
FILE: ury/install.py
function after_install (line 6) | def after_install():
FILE: ury/patches/v2_0/default_permissions.py
function execute (line 12) | def execute():
function apply_permissions (line 158) | def apply_permissions(doctype, role, perms):
FILE: ury/permission.py
function check_app_permission (line 3) | def check_app_permission():
FILE: ury/public/js/jsrsasign-all-min.js
function n (line 21) | function n() { }
function j (line 99) | function j(b, c) { var a = (this._lBlock >>> b ^ this._rBlock) & c; this...
function l (line 99) | function l(b, c) { var a = (this._rBlock >>> b ^ this._lBlock) & c; this...
function h (line 173) | function h(a, f, g, j, p, h, k) { a = a + (f & g | ~f & j) + p + k; retu...
function k (line 173) | function k(a, f, g, j, p, h, k) { a = a + (f & j | g & ~j) + p + k; retu...
function l (line 173) | function l(a, f, g, j, h, k, l) { a = a + (f ^ g ^ j) + h + l; return (a...
function n (line 173) | function n(a, f, g, j, h, k, l) { a = a + (g ^ (f | ~j)) + h + l; return...
function a (line 239) | function a() { return d.create.apply(d, arguments) }
function hex2b64 (line 334) | function hex2b64(d) { var b; var e; var a = ""; for (b = 0; b + 3 <= d.l...
function b64tohex (line 334) | function b64tohex(f) { var d = ""; var e; var b = 0; var c; var a; for (...
function b64toBA (line 334) | function b64toBA(e) { var d = b64tohex(e); var c; var b = new Array(); f...
function BigInteger (line 337) | function BigInteger(e, d, f) { if (e != null) { if ("number" == typeof e...
function nbi (line 337) | function nbi() { return new BigInteger(null) }
function am1 (line 337) | function am1(f, a, b, e, h, g) { while (--g >= 0) { var d = a * this[f++...
function am2 (line 337) | function am2(f, q, r, e, o, a) { var k = q & 32767, p = q >> 15; while (...
function am3 (line 337) | function am3(f, q, r, e, o, a) { var k = q & 16383, p = q >> 14; while (...
function int2char (line 337) | function int2char(a) { return BI_RM.charAt(a) }
function intAt (line 337) | function intAt(b, a) { var d = BI_RC[b.charCodeAt(a)]; return (d == null...
function bnpCopyTo (line 337) | function bnpCopyTo(b) { for (var a = this.t - 1; a >= 0; --a) { b[a] = t...
function bnpFromInt (line 337) | function bnpFromInt(a) { this.t = 1; this.s = (a < 0) ? -1 : 0; if (a > ...
function nbv (line 337) | function nbv(a) { var b = nbi(); b.fromInt(a); return b }
function bnpFromString (line 337) | function bnpFromString(h, c) { var e; if (c == 16) { e = 4 } else { if (...
function bnpClamp (line 337) | function bnpClamp() { var a = this.s & this.DM; while (this.t > 0 && thi...
function bnToString (line 337) | function bnToString(c) { if (this.s < 0) { return "-" + this.negate().to...
function bnNegate (line 337) | function bnNegate() { var a = nbi(); BigInteger.ZERO.subTo(this, a); ret...
function bnAbs (line 337) | function bnAbs() { return (this.s < 0) ? this.negate() : this }
function bnCompareTo (line 337) | function bnCompareTo(b) { var d = this.s - b.s; if (d != 0) { return d }...
function nbits (line 337) | function nbits(a) { var c = 1, b; if ((b = a >>> 16) != 0) { a = b; c +=...
function bnBitLength (line 337) | function bnBitLength() { if (this.t <= 0) { return 0 } return this.DB * ...
function bnpDLShiftTo (line 337) | function bnpDLShiftTo(c, b) { var a; for (a = this.t - 1; a >= 0; --a) {...
function bnpDRShiftTo (line 337) | function bnpDRShiftTo(c, b) { for (var a = c; a < this.t; ++a) { b[a - c...
function bnpLShiftTo (line 337) | function bnpLShiftTo(j, e) { var b = j % this.DB; var a = this.DB - b; v...
function bnpRShiftTo (line 337) | function bnpRShiftTo(g, d) { d.s = this.s; var e = Math.floor(g / this.D...
function bnpSubTo (line 337) | function bnpSubTo(d, f) { var e = 0, g = 0, b = Math.min(d.t, this.t); w...
function bnpMultiplyTo (line 337) | function bnpMultiplyTo(c, e) { var b = this.abs(), f = c.abs(); var d = ...
function bnpSquareTo (line 337) | function bnpSquareTo(d) { var a = this.abs(); var b = d.t = 2 * a.t; whi...
function bnpDivRemTo (line 337) | function bnpDivRemTo(n, h, g) { var w = n.abs(); if (w.t <= 0) { return ...
function bnMod (line 337) | function bnMod(b) { var c = nbi(); this.abs().divRemTo(b, null, c); if (...
function Classic (line 337) | function Classic(a) { this.m = a }
function cConvert (line 337) | function cConvert(a) { if (a.s < 0 || a.compareTo(this.m) >= 0) { return...
function cRevert (line 337) | function cRevert(a) { return a }
function cReduce (line 337) | function cReduce(a) { a.divRemTo(this.m, null, a) }
function cMulTo (line 337) | function cMulTo(a, c, b) { a.multiplyTo(c, b); this.reduce(b) }
function cSqrTo (line 337) | function cSqrTo(a, b) { a.squareTo(b); this.reduce(b) }
function bnpInvDigit (line 337) | function bnpInvDigit() { if (this.t < 1) { return 0 } var a = this[0]; i...
function Montgomery (line 337) | function Montgomery(a) { this.m = a; this.mp = a.invDigit(); this.mpl = ...
function montConvert (line 337) | function montConvert(a) { var b = nbi(); a.abs().dlShiftTo(this.m.t, b);...
function montRevert (line 337) | function montRevert(a) { var b = nbi(); a.copyTo(b); this.reduce(b); ret...
function montReduce (line 337) | function montReduce(a) { while (a.t <= this.mt2) { a[a.t++] = 0 } for (v...
function montSqrTo (line 337) | function montSqrTo(a, b) { a.squareTo(b); this.reduce(b) }
function montMulTo (line 337) | function montMulTo(a, c, b) { a.multiplyTo(c, b); this.reduce(b) }
function bnpIsEven (line 337) | function bnpIsEven() { return ((this.t > 0) ? (this[0] & 1) : this.s) ==...
function bnpExp (line 337) | function bnpExp(h, j) { if (h > 4294967295 || h < 1) { return BigInteger...
function bnModPowInt (line 337) | function bnModPowInt(b, a) { var c; if (b < 256 || a.isEven()) { c = new...
function bnClone (line 340) | function bnClone() { var a = nbi(); this.copyTo(a); return a }
function bnIntValue (line 340) | function bnIntValue() { if (this.s < 0) { if (this.t == 1) { return this...
function bnByteValue (line 340) | function bnByteValue() { return (this.t == 0) ? this.s : (this[0] << 24)...
function bnShortValue (line 340) | function bnShortValue() { return (this.t == 0) ? this.s : (this[0] << 16...
function bnpChunkSize (line 340) | function bnpChunkSize(a) { return Math.floor(Math.LN2 * this.DB / Math.l...
function bnSigNum (line 340) | function bnSigNum() { if (this.s < 0) { return -1 } else { if (this.t <=...
function bnpToRadix (line 340) | function bnpToRadix(c) { if (c == null) { c = 10 } if (this.signum() == ...
function bnpFromRadix (line 340) | function bnpFromRadix(m, h) { this.fromInt(0); if (h == null) { h = 10 }...
function bnpFromNumber (line 340) | function bnpFromNumber(f, e, h) { if ("number" == typeof e) { if (f < 2)...
function bnToByteArray (line 340) | function bnToByteArray() { var b = this.t, c = new Array(); c[0] = this....
function bnEquals (line 340) | function bnEquals(b) { return (this.compareTo(b) == 0) }
function bnMin (line 340) | function bnMin(b) { return (this.compareTo(b) < 0) ? this : b }
function bnMax (line 340) | function bnMax(b) { return (this.compareTo(b) > 0) ? this : b }
function bnpBitwiseTo (line 340) | function bnpBitwiseTo(c, h, e) { var d, g, b = Math.min(c.t, this.t); fo...
function op_and (line 340) | function op_and(a, b) { return a & b }
function bnAnd (line 340) | function bnAnd(b) { var c = nbi(); this.bitwiseTo(b, op_and, c); return c }
function op_or (line 340) | function op_or(a, b) { return a | b }
function bnOr (line 340) | function bnOr(b) { var c = nbi(); this.bitwiseTo(b, op_or, c); return c }
function op_xor (line 340) | function op_xor(a, b) { return a ^ b }
function bnXor (line 340) | function bnXor(b) { var c = nbi(); this.bitwiseTo(b, op_xor, c); return c }
function op_andnot (line 340) | function op_andnot(a, b) { return a & ~b }
function bnAndNot (line 340) | function bnAndNot(b) { var c = nbi(); this.bitwiseTo(b, op_andnot, c); r...
function bnNot (line 340) | function bnNot() { var b = nbi(); for (var a = 0; a < this.t; ++a) { b[a...
function bnShiftLeft (line 340) | function bnShiftLeft(b) { var a = nbi(); if (b < 0) { this.rShiftTo(-b, ...
function bnShiftRight (line 340) | function bnShiftRight(b) { var a = nbi(); if (b < 0) { this.lShiftTo(-b,...
function lbit (line 340) | function lbit(a) { if (a == 0) { return -1 } var b = 0; if ((a & 65535) ...
function bnGetLowestSetBit (line 340) | function bnGetLowestSetBit() { for (var a = 0; a < this.t; ++a) { if (th...
function cbit (line 340) | function cbit(a) { var b = 0; while (a != 0) { a &= a - 1; ++b } return b }
function bnBitCount (line 340) | function bnBitCount() { var c = 0, a = this.s & this.DM; for (var b = 0;...
function bnTestBit (line 340) | function bnTestBit(b) { var a = Math.floor(b / this.DB); if (a >= this.t...
function bnpChangeBit (line 340) | function bnpChangeBit(c, b) { var a = BigInteger.ONE.shiftLeft(c); this....
function bnSetBit (line 340) | function bnSetBit(a) { return this.changeBit(a, op_or) }
function bnClearBit (line 340) | function bnClearBit(a) { return this.changeBit(a, op_andnot) }
function bnFlipBit (line 340) | function bnFlipBit(a) { return this.changeBit(a, op_xor) }
function bnpAddTo (line 340) | function bnpAddTo(d, f) { var e = 0, g = 0, b = Math.min(d.t, this.t); w...
function bnAdd (line 340) | function bnAdd(b) { var c = nbi(); this.addTo(b, c); return c }
function bnSubtract (line 340) | function bnSubtract(b) { var c = nbi(); this.subTo(b, c); return c }
function bnMultiply (line 340) | function bnMultiply(b) { var c = nbi(); this.multiplyTo(b, c); return c }
function bnSquare (line 340) | function bnSquare() { var a = nbi(); this.squareTo(a); return a }
function bnDivide (line 340) | function bnDivide(b) { var c = nbi(); this.divRemTo(b, c, null); return c }
function bnRemainder (line 340) | function bnRemainder(b) { var c = nbi(); this.divRemTo(b, null, c); retu...
function bnDivideAndRemainder (line 340) | function bnDivideAndRemainder(b) { var d = nbi(), c = nbi(); this.divRem...
function bnpDMultiply (line 340) | function bnpDMultiply(a) { this[this.t] = this.am(0, a - 1, this, 0, 0, ...
function bnpDAddOffset (line 340) | function bnpDAddOffset(b, a) { if (b == 0) { return } while (this.t <= a...
function NullExp (line 340) | function NullExp() { }
function nNop (line 340) | function nNop(a) { return a }
function nMulTo (line 340) | function nMulTo(a, c, b) { a.multiplyTo(c, b) }
function nSqrTo (line 340) | function nSqrTo(a, b) { a.squareTo(b) }
function bnPow (line 340) | function bnPow(a) { return this.exp(a, new NullExp()) }
function bnpMultiplyLowerTo (line 340) | function bnpMultiplyLowerTo(b, f, e) { var d = Math.min(this.t + b.t, f)...
function bnpMultiplyUpperTo (line 340) | function bnpMultiplyUpperTo(b, e, d) { --e; var c = d.t = this.t + b.t -...
function Barrett (line 340) | function Barrett(a) { this.r2 = nbi(); this.q3 = nbi(); BigInteger.ONE.d...
function barrettConvert (line 340) | function barrettConvert(a) { if (a.s < 0 || a.t > 2 * this.m.t) { return...
function barrettRevert (line 340) | function barrettRevert(a) { return a }
function barrettReduce (line 340) | function barrettReduce(a) { a.drShiftTo(this.m.t - 1, this.r2); if (a.t ...
function barrettSqrTo (line 340) | function barrettSqrTo(a, b) { a.squareTo(b); this.reduce(b) }
function barrettMulTo (line 340) | function barrettMulTo(a, c, b) { a.multiplyTo(c, b); this.reduce(b) }
function bnModPow (line 340) | function bnModPow(q, f) { var o = q.bitLength(), h, b = nbv(1), v; if (o...
function bnGCD (line 340) | function bnGCD(c) { var b = (this.s < 0) ? this.negate() : this.clone();...
function bnpModInt (line 340) | function bnpModInt(e) { if (e <= 0) { return 0 } var c = this.DV % e, b ...
function bnModInverse (line 340) | function bnModInverse(f) { var j = f.isEven(); if ((this.isEven() && j) ...
function bnIsProbablePrime (line 340) | function bnIsProbablePrime(e) { var d, b = this.abs(); if (b.t == 1 && b...
function bnpMillerRabin (line 340) | function bnpMillerRabin(f) { var g = this.subtract(BigInteger.ONE); var ...
function Arcfour (line 343) | function Arcfour() { this.i = 0; this.j = 0; this.S = new Array() }
function ARC4init (line 343) | function ARC4init(d) { var c, a, b; for (c = 0; c < 256; ++c) { this.S[c...
function ARC4next (line 343) | function ARC4next() { var a; this.i = (this.i + 1) & 255; this.j = (this...
function prng_newstate (line 343) | function prng_newstate() { return new Arcfour() }
function rng_seed_int (line 346) | function rng_seed_int(a) { rng_pool[rng_pptr++] ^= a & 255; rng_pool[rng...
function rng_seed_time (line 346) | function rng_seed_time() { rng_seed_int(new Date().getTime()) }
function rng_get_byte (line 346) | function rng_get_byte() { if (rng_state == null) { rng_seed_time(); rng_...
function rng_get_bytes (line 346) | function rng_get_bytes(b) { var a; for (a = 0; a < b.length; ++a) { b[a]...
function SecureRandom (line 346) | function SecureRandom() { }
function parseBigInt (line 349) | function parseBigInt(b, a) { return new BigInteger(b, a) }
function linebrk (line 349) | function linebrk(c, d) { var a = ""; var b = 0; while (b + d < c.length)...
function byte2Hex (line 349) | function byte2Hex(a) { if (a < 16) { return "0" + a.toString(16) } else ...
function pkcs1pad2 (line 349) | function pkcs1pad2(e, h) { if (h < e.length + 11) { alert("Message too l...
function oaep_mgf1_arr (line 349) | function oaep_mgf1_arr(c, a, e) { var b = "", d = 0; while (b.length < a...
function oaep_pad (line 349) | function oaep_pad(q, a, f, l) { var c = KJUR.crypto.MessageDigest; var o...
function RSAKey (line 349) | function RSAKey() { this.n = null; this.e = 0; this.d = null; this.p = n...
function RSASetPublic (line 349) | function RSASetPublic(b, a) { this.isPublic = true; this.isPrivate = fal...
function RSADoPublic (line 349) | function RSADoPublic(a) { return a.modPowInt(this.e, this.n) }
function RSAEncrypt (line 349) | function RSAEncrypt(d) { var a = pkcs1pad2(d, (this.n.bitLength() + 7) >...
function RSAEncryptOAEP (line 349) | function RSAEncryptOAEP(f, e, b) { var a = oaep_pad(f, (this.n.bitLength...
function pkcs1unpad2 (line 352) | function pkcs1unpad2(g, j) { var a = g.toByteArray(); var f = 0; while (...
function oaep_mgf1_str (line 352) | function oaep_mgf1_str(c, a, e) { var b = "", d = 0; while (b.length < a...
function oaep_unpad (line 352) | function oaep_unpad(o, b, g, p) { var e = KJUR.crypto.MessageDigest; var...
function RSASetPrivate (line 352) | function RSASetPrivate(c, a, b) { this.isPrivate = true; if (typeof c !=...
function RSASetPrivateEx (line 352) | function RSASetPrivateEx(g, d, e, c, b, a, h, f) { this.isPrivate = true...
function RSAGenerate (line 352) | function RSAGenerate(b, i) { var a = new SecureRandom(); var f = b >> 1;...
function RSADoPrivate (line 352) | function RSADoPrivate(a) { if (this.p == null || this.q == null) { retur...
function RSADecrypt (line 352) | function RSADecrypt(b) { var d = parseBigInt(b, 16); var a = this.doPriv...
function RSADecryptOAEP (line 352) | function RSADecryptOAEP(e, d, b) { var f = parseBigInt(e, 16); var a = t...
function ECFieldElementFp (line 355) | function ECFieldElementFp(b, a) { this.x = a; this.q = b }
function feFpEquals (line 355) | function feFpEquals(a) { if (a == this) { return true } return (this.q.e...
function feFpToBigInteger (line 355) | function feFpToBigInteger() { return this.x }
function feFpNegate (line 355) | function feFpNegate() { return new ECFieldElementFp(this.q, this.x.negat...
function feFpAdd (line 355) | function feFpAdd(a) { return new ECFieldElementFp(this.q, this.x.add(a.t...
function feFpSubtract (line 355) | function feFpSubtract(a) { return new ECFieldElementFp(this.q, this.x.su...
function feFpMultiply (line 355) | function feFpMultiply(a) { return new ECFieldElementFp(this.q, this.x.mu...
function feFpSquare (line 355) | function feFpSquare() { return new ECFieldElementFp(this.q, this.x.squar...
function feFpDivide (line 355) | function feFpDivide(a) { return new ECFieldElementFp(this.q, this.x.mult...
function ECPointFp (line 355) | function ECPointFp(c, a, d, b) { this.curve = c; this.x = a; this.y = d;...
function pointFpGetX (line 355) | function pointFpGetX() { if (this.zinv == null) { this.zinv = this.z.mod...
function pointFpGetY (line 355) | function pointFpGetY() { if (this.zinv == null) { this.zinv = this.z.mod...
function pointFpEquals (line 355) | function pointFpEquals(a) { if (a == this) { return true } if (this.isIn...
function pointFpIsInfinity (line 355) | function pointFpIsInfinity() { if ((this.x == null) && (this.y == null))...
function pointFpNegate (line 355) | function pointFpNegate() { return new ECPointFp(this.curve, this.x, this...
function pointFpAdd (line 355) | function pointFpAdd(l) { if (this.isInfinity()) { return l } if (l.isInf...
function pointFpTwice (line 355) | function pointFpTwice() { if (this.isInfinity()) { return this } if (thi...
function pointFpMultiply (line 355) | function pointFpMultiply(b) { if (this.isInfinity()) { return this } if ...
function pointFpMultiplyTwo (line 355) | function pointFpMultiplyTwo(c, a, b) { var d; if (c.bitLength() > b.bitL...
function ECCurveFp (line 355) | function ECCurveFp(e, d, c) { this.q = e; this.a = this.fromBigInteger(d...
function curveFpGetQ (line 355) | function curveFpGetQ() { return this.q }
function curveFpGetA (line 355) | function curveFpGetA() { return this.a }
function curveFpGetB (line 355) | function curveFpGetB() { return this.b }
function curveFpEquals (line 355) | function curveFpEquals(a) { if (a == this) { return true } return (this....
function curveFpGetInfinity (line 355) | function curveFpGetInfinity() { return this.infinity }
function curveFpFromBigInteger (line 355) | function curveFpFromBigInteger(a) { return new ECFieldElementFp(this.q, ...
function curveFpDecodePointHex (line 355) | function curveFpDecodePointHex(d) { switch (parseInt(d.substr(0, 2), 16)...
function h (line 361) | function h(l, m, n) { return m ? g[m] : String.fromCharCode(parseInt(n, ...
function Base64x (line 370) | function Base64x() { }
function stoBA (line 370) | function stoBA(d) { var b = new Array(); for (var c = 0; c < d.length; c...
function BAtos (line 370) | function BAtos(b) { var d = ""; for (var c = 0; c < b.length; c++) { d =...
function BAtohex (line 370) | function BAtohex(b) { var e = ""; for (var d = 0; d < b.length; d++) { v...
function stohex (line 370) | function stohex(a) { return BAtohex(stoBA(a)) }
function stob64 (line 370) | function stob64(a) { return hex2b64(stohex(a)) }
function stob64u (line 370) | function stob64u(a) { return b64tob64u(hex2b64(stohex(a))) }
function b64utos (line 370) | function b64utos(a) { return BAtos(b64toBA(b64utob64(a))) }
function b64tob64u (line 370) | function b64tob64u(a) { a = a.replace(/\=/g, ""); a = a.replace(/\+/g, "...
function b64utob64 (line 370) | function b64utob64(a) { if (a.length % 4 == 2) { a = a + "==" } else { i...
function hextob64u (line 370) | function hextob64u(a) { if (a.length % 2 == 1) { a = "0" + a } return b6...
function b64utohex (line 370) | function b64utohex(a) { return b64tohex(b64utob64(a)) }
function utf8tob64 (line 370) | function utf8tob64(a) { return hex2b64(uricmptohex(encodeURIComponentAll...
function b64toutf8 (line 370) | function b64toutf8(a) { return decodeURIComponent(hextouricmp(b64tohex(a...
function utf8tohex (line 370) | function utf8tohex(a) { return uricmptohex(encodeURIComponentAll(a)) }
function hextoutf8 (line 370) | function hextoutf8(a) { return decodeURIComponent(hextouricmp(a)) }
function hextorstr (line 370) | function hextorstr(c) { var b = ""; for (var a = 0; a < c.length - 1; a ...
function rstrtohex (line 370) | function rstrtohex(c) { var a = ""; for (var b = 0; b < c.length; b++) {...
function hextob64 (line 370) | function hextob64(a) { return hex2b64(a) }
function hextob64nl (line 370) | function hextob64nl(b) { var a = hextob64(b); var c = a.replace(/(.{64})...
function b64nltohex (line 370) | function b64nltohex(b) { var a = b.replace(/[^0-9A-Za-z\/+=]*/g, ""); va...
function hextopem (line 370) | function hextopem(a, b) { var c = hextob64nl(a); return "-----BEGIN " + ...
function pemtohex (line 370) | function pemtohex(a, b) { if (a.indexOf("-----BEGIN ") == -1) { throw "c...
function hextoArrayBuffer (line 370) | function hextoArrayBuffer(d) { if (d.length % 2 != 0) { throw "input is ...
function ArrayBuffertohex (line 370) | function ArrayBuffertohex(b) { var d = ""; var a = new DataView(b); for ...
function zulutomsec (line 370) | function zulutomsec(n) { var l, j, m, e, f, i, b, k; var a, h, g, c; c =...
function zulutosec (line 370) | function zulutosec(a) { var b = zulutomsec(a); return ~~(b / 1000) }
function zulutodate (line 370) | function zulutodate(a) { return new Date(zulutomsec(a)) }
function datetozulu (line 370) | function datetozulu(g, e, f) { var b; var a = g.getUTCFullYear(); if (e)...
function uricmptohex (line 370) | function uricmptohex(a) { return a.replace(/%/g, "") }
function hextouricmp (line 370) | function hextouricmp(a) { return a.replace(/(..)/g, "%$1") }
function encodeURIComponentAll (line 370) | function encodeURIComponentAll(a) { var d = encodeURIComponent(a); var b...
function newline_toUnix (line 370) | function newline_toUnix(a) { a = a.replace(/\r\n/mg, "\n"); return a }
function newline_toDos (line 370) | function newline_toDos(a) { a = a.replace(/\r\n/mg, "\n"); a = a.replace...
function hextoposhex (line 370) | function hextoposhex(a) { if (a.length % 2 == 1) { return "0" + a } if (...
function intarystrtohex (line 370) | function intarystrtohex(b) { b = b.replace(/^\s*\[\s*/, ""); b = b.repla...
function c (line 372) | function c(s, o, r, n) { var j = Math.max(o.bitLength(), n.bitLength());...
function a (line 373) | function a(d) { return new BigInteger(d, 16) }
function A (line 375) | function A(s) { var G = l({ seq: [{ "int": 0 }, { "int": { bigint: s.n }...
function B (line 375) | function B(G) { var s = l({ seq: [{ "int": 1 }, { octstr: { hex: G.prvKe...
function x (line 375) | function x(s) { var G = l({ seq: [{ "int": 0 }, { "int": { bigint: s.p }...
function _rsasign_getHexPaddedDigestInfoForString (line 377) | function _rsasign_getHexPaddedDigestInfoForString(d, e, a) { var b = fun...
function _zeroPaddingOfSignature (line 377) | function _zeroPaddingOfSignature(e, d) { var c = ""; var a = d / 4 - e.l...
function pss_mgf1_str (line 377) | function pss_mgf1_str(c, a, e) { var b = "", d = 0; while (b.length < a)...
function _rsasign_getDecryptSignatureBI (line 377) | function _rsasign_getDecryptSignatureBI(a, d, c) { var b = new RSAKey();...
function _rsasign_getHexDigestInfoFromSig (line 377) | function _rsasign_getHexDigestInfoFromSig(a, c, b) { var e = _rsasign_ge...
function _rsasign_getAlgNameAndHashFromHexDisgestInfo (line 377) | function _rsasign_getAlgNameAndHashFromHexDisgestInfo(f) { for (var e in...
function X509 (line 378) | function X509() { var k = ASN1HEX, j = k.getChildIdx, h = k.getV, b = k....
FILE: ury/public/js/pos_extend.js
method constructor (line 13) | constructor(wrapper) {
method make_filter_section (line 17) | make_filter_section() {
method refresh_list (line 46) | refresh_list() {
method constructor (line 72) | constructor(wrapper) {
method prepare_menu (line 75) | prepare_menu() {
method cancel_order (line 92) | cancel_order() {
method cancel (line 145) | cancel() {
method constructor (line 166) | constructor(wrapper) {
method get_invoice_html (line 169) | get_invoice_html(invoice) {
method constructor (line 202) | constructor(wrapper) {
method bind_events (line 205) | bind_events() {
method constructor (line 285) | constructor(wrapper) {
method init_cart_components (line 288) | init_cart_components() {
method make_cart_totals_section (line 336) | make_cart_totals_section() {
method toggle_checkout_btn (line 365) | toggle_checkout_btn(show_checkout) {
FILE: ury/public/js/pos_print.js
function printWithQZTray (line 76) | function printWithQZTray() {
FILE: ury/public/js/quick_entry.js
method constructor (line 3) | constructor(doctype, after_insert, init_callback, doc, force) {
method render_dialog (line 9) | render_dialog() {
method getfields (line 15) | getfields() {
FILE: ury/public/js/qz-tray.js
function sendCert (line 334) | function sendCert(cert) {
function skipKeys (line 627) | function skipKeys(key, value) {
function Config (line 1036) | function Config(printer, opts) {
FILE: ury/public/js/restrict_qty_edit_pos.js
method onload (line 2) | async onload(frm) {
FILE: ury/setup.py
function after_install (line 8) | def after_install():
function before_uninstall (line 11) | def before_uninstall():
function get_custom_fields (line 14) | def get_custom_fields():
function delete_custom_fields (line 369) | def delete_custom_fields(custom_fields):
FILE: ury/uninstall.py
function before_uninstall (line 7) | def before_uninstall():
function uninstall (line 15) | def uninstall():
FILE: ury/ury/api/button_permission.py
function cancel_check (line 5) | def cancel_check():
FILE: ury/ury/api/pos_extend.py
function validate_search_input (line 4) | def validate_search_input(search_term):
function overrided_past_order_list (line 21) | def overrided_past_order_list(search_term, status, limit=20):
FILE: ury/ury/api/ury_kot_display.py
function serve_kot (line 10) | def serve_kot(name, time):
function confirm_cancel_kot (line 23) | def confirm_cancel_kot(name, user):
function get_site_name (line 29) | def get_site_name():
function kot_list (line 33) | def kot_list():
function served_kot_list (line 82) | def served_kot_list():
FILE: ury/ury/api/ury_kot_generate.py
function load_json (line 8) | def load_json(data):
function create_order_items (line 15) | def create_order_items(items):
function create_kot_doc (line 29) | def create_kot_doc(
function get_all_production_item_groups (line 86) | def get_all_production_item_groups(branch):
function process_items_for_kot (line 110) | def process_items_for_kot(
function process_items_for_cancel_kot (line 187) | def process_items_for_cancel_kot(
function create_cancel_kot_doc (line 233) | def create_cancel_kot_doc(
function kot_execute (line 323) | def kot_execute(
function compare_two_array (line 380) | def compare_two_array(array_1, array_2):
function get_removed_items (line 398) | def get_removed_items(array_1, array_2):
FILE: ury/ury/api/ury_kot_notification.py
function get_users_with_role (line 4) | def get_users_with_role(role_name):
function order_delay_notification (line 20) | def order_delay_notification(id):
function create_system_notification (line 66) | def create_system_notification(message, user, subject):
FILE: ury/ury/api/ury_kot_order_number.py
function set_order_number (line 4) | def set_order_number(doc, event):
function set_last_invoice_in_pos_open (line 86) | def set_last_invoice_in_pos_open(doc, event):
FILE: ury/ury/api/ury_kot_reprint.py
function reprint_kot (line 8) | def reprint_kot(invoice_number):
function print_kot (line 46) | def print_kot(printer,docname, kot_print_format):
FILE: ury/ury/api/ury_kot_validation.py
function kotValidationThread (line 7) | def kotValidationThread():
function get_unprocessed_invoices (line 21) | def get_unprocessed_invoices(start_time, end_time):
function process_invoice (line 35) | def process_invoice(invoice):
function get_productions_for_branch (line 83) | def get_productions_for_branch(branch):
function create_kot (line 90) | def create_kot(
function create_kot_log (line 125) | def create_kot_log(kotdoc, invoice):
FILE: ury/ury/api/ury_menu_course_validation.py
function validate_priority (line 3) | def validate_priority(doc,event):
FILE: ury/ury/api/ury_print.py
function network_printing (line 17) | def network_printing(
function select_network_printer (line 85) | def select_network_printer(pos_profile, invoice_id):
function qz_print_update (line 112) | def qz_print_update(invoice):
function print_pos_page (line 156) | def print_pos_page(doctype, name, print_format):
function qz_certificate (line 179) | def qz_certificate():
function signature_promise (line 187) | def signature_promise():
FILE: ury/ury/doctype/aggregator_settings/aggregator_settings.py
class AggregatorSettings (line 7) | class AggregatorSettings(Document):
FILE: ury/ury/doctype/item_add_on/item_add_on.py
class ItemAddOn (line 8) | class ItemAddOn(Document):
FILE: ury/ury/doctype/menu_for_room/menu_for_room.py
class MenuforRoom (line 7) | class MenuforRoom(Document):
FILE: ury/ury/doctype/menu_for_room/test_menu_for_room.py
class TestMenuforRoom (line 8) | class TestMenuforRoom(FrappeTestCase):
FILE: ury/ury/doctype/multiple_rooms/multiple_rooms.py
class MultipleRooms (line 7) | class MultipleRooms(Document):
FILE: ury/ury/doctype/order_type_menu/order_type_menu.py
class OrderTypeMenu (line 7) | class OrderTypeMenu(Document):
FILE: ury/ury/doctype/pos_item_variants/pos_item_variants.py
class POSItemVariants (line 8) | class POSItemVariants(Document):
FILE: ury/ury/doctype/role_permitted/role_permitted.py
class RolePermitted (line 7) | class RolePermitted(Document):
FILE: ury/ury/doctype/sub_pos_closing/sub_pos_closing.js
function padNumber (line 13) | function padNumber(num) {
method pos_opening_entry (line 93) | pos_opening_entry(frm) {
method set_opening_amounts (line 110) | set_opening_amounts(frm) {
method get_pos_invoices (line 125) | get_pos_invoices(frm) {
function set_form_data (line 183) | function set_form_data(data, frm) {
function add_to_pos_transaction (line 193) | function add_to_pos_transaction(d, frm) {
function refresh_payments (line 202) | function refresh_payments(d, frm) {
function reset_values (line 222) | function reset_values(frm) {
function refresh_fields (line 227) | function refresh_fields(frm) {
FILE: ury/ury/doctype/sub_pos_closing/sub_pos_closing.py
class SubPOSClosing (line 14) | class SubPOSClosing(Document):
method validate (line 15) | def validate(self):
method on_submit (line 70) | def on_submit(self):
method on_cancel (line 76) | def on_cancel(self):
function get_pos_profile (line 84) | def get_pos_profile():
function get_cashiers (line 92) | def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
function get_pos_invoices (line 100) | def get_pos_invoices(start, end, pos_profile, user):
FILE: ury/ury/doctype/sub_pos_closing/test_sub_pos_closing.py
class TestSubPOSClosing (line 8) | class TestSubPOSClosing(FrappeTestCase):
FILE: ury/ury/doctype/sub_pos_closing_payment/sub_pos_closing_payment.py
class SubPOSClosingPayment (line 7) | class SubPOSClosingPayment(Document):
FILE: ury/ury/doctype/sub_pos_invoices/sub_pos_invoices.py
class SubPOSInvoices (line 7) | class SubPOSInvoices(Document):
FILE: ury/ury/doctype/ury_cost_of_goods/ury_cost_of_goods.py
class URYCostOfGoods (line 7) | class URYCostOfGoods(Document):
FILE: ury/ury/doctype/ury_daily_p_and_l/test_ury_daily_p_and_l.py
class TestURYDailyPandL (line 8) | class TestURYDailyPandL(FrappeTestCase):
FILE: ury/ury/doctype/ury_daily_p_and_l/ury_daily_p_and_l.js
function set_html_data (line 60) | function set_html_data(frm) {
function set_electricity_closing (line 72) | function set_electricity_closing(frm) {
FILE: ury/ury/doctype/ury_daily_p_and_l/ury_daily_p_and_l.py
function inner_bom_process (line 10) | def inner_bom_process(buying_price_list, bom):
function inner_inner_bom_process (line 42) | def inner_inner_bom_process(buying_price_list, bom):
class URYDailyPandL (line 60) | class URYDailyPandL(Document):
method cogs_sold (line 61) | def cogs_sold(self):
method before_save (line 274) | def before_save(self):
method before_submit (line 279) | def before_submit(self):
method get_proft_loss_details (line 538) | def get_proft_loss_details(self):
FILE: ury/ury/doctype/ury_fixed_expenses/ury_fixed_expenses.py
class URYFixedExpenses (line 7) | class URYFixedExpenses(Document):
FILE: ury/ury/doctype/ury_kot/test_ury_kot.py
class TestURYKOT (line 8) | class TestURYKOT(FrappeTestCase):
FILE: ury/ury/doctype/ury_kot/ury_kot.py
class URYKOT (line 11) | class URYKOT(Document):
method on_submit (line 12) | def on_submit(self):
method before_submit (line 16) | def before_submit(self):
method multi_print_kot (line 20) | def multi_print_kot(self):
method kotDisplayRealtime (line 87) | def kotDisplayRealtime(self):
method userSetting (line 103) | def userSetting(self):
FILE: ury/ury/doctype/ury_kot_error_log/test_ury_kot_error_log.py
class TestURYKOTErrorLog (line 8) | class TestURYKOTErrorLog(FrappeTestCase):
FILE: ury/ury/doctype/ury_kot_error_log/ury_kot_error_log.py
class URYKOTErrorLog (line 7) | class URYKOTErrorLog(Document):
FILE: ury/ury/doctype/ury_kot_items/ury_kot_items.py
class URYKOTItems (line 7) | class URYKOTItems(Document):
FILE: ury/ury/doctype/ury_materials/ury_materials.py
class URYMaterials (line 7) | class URYMaterials(Document):
FILE: ury/ury/doctype/ury_menu/test_ury_menu.py
class TestURYMenu (line 8) | class TestURYMenu(FrappeTestCase):
FILE: ury/ury/doctype/ury_menu/ury_menu.py
class URYMenu (line 8) | class URYMenu(Document):
method validate (line 9) | def validate(self):
method on_update (line 14) | def on_update(self):
method on_trash (line 18) | def on_trash(self):
method clear_item_price (line 22) | def clear_item_price(self, price_list=None):
method make_price_list (line 28) | def make_price_list(self):
method get_price_list (line 46) | def get_price_list(self):
FILE: ury/ury/doctype/ury_menu_course/test_ury_menu_course.py
class TestURYMenuCourse (line 8) | class TestURYMenuCourse(FrappeTestCase):
FILE: ury/ury/doctype/ury_menu_course/ury_menu_course.py
class URYMenuCourse (line 7) | class URYMenuCourse(Document):
FILE: ury/ury/doctype/ury_menu_item/ury_menu_item.py
class URYMenuItem (line 8) | class URYMenuItem(Document):
FILE: ury/ury/doctype/ury_notification_recipient/test_ury_notification_recipient.py
class TestURYNotificationRecipient (line 8) | class TestURYNotificationRecipient(FrappeTestCase):
FILE: ury/ury/doctype/ury_notification_recipient/ury_notification_recipient.py
class URYNotificationRecipient (line 7) | class URYNotificationRecipient(Document):
FILE: ury/ury/doctype/ury_order/test_ury_order.py
class TestURYOrder (line 8) | class TestURYOrder(FrappeTestCase):
FILE: ury/ury/doctype/ury_order/ury_order.js
function handleTabClick (line 225) | function handleTabClick(tabId, message) {
method item_search (line 275) | item_search(frm) {
method primary_action (line 379) | primary_action(values) {
function printWithQZTray (line 857) | function printWithQZTray() {
method primary_action (line 1086) | primary_action(values) {
method primary_action (line 1154) | primary_action(values) {
FILE: ury/ury/doctype/ury_order/ury_order.py
class URYOrder (line 16) | class URYOrder(Document):
function get_order_invoice (line 21) | def get_order_invoice(table=None, invoiceNo=None, order_type=None, is_pa...
function sync_order (line 114) | def sync_order(
function item_query_restaurant (line 309) | def item_query_restaurant(
function get_restaurant_and_menu_name (line 328) | def get_restaurant_and_menu_name(table):
function get_menu_name (line 360) | def get_menu_name(order_type):
function pos_opening_check (line 385) | def pos_opening_check():
function table_transfer (line 430) | def table_transfer(table, newTable, invoice):
function captain_transfer (line 469) | def captain_transfer(currentCaptain, newCaptain, invoice):
function customer_favourite_item (line 504) | def customer_favourite_item(customer_name):
function cancel_order (line 528) | def cancel_order(invoice_id, reason):
function make_invoice (line 558) | def make_invoice(customer, payments, cashier, pos_profile,owner, additio...
function cancel_kot (line 589) | def cancel_kot(invoice_id):
function change_table_in_kot (line 642) | def change_table_in_kot(invoice, new_table, branch):
FILE: ury/ury/doctype/ury_order_item/test_ury_order_item.py
class TestURYOrderItem (line 8) | class TestURYOrderItem(FrappeTestCase):
FILE: ury/ury/doctype/ury_order_item/ury_order_item.py
class URYOrderItem (line 8) | class URYOrderItem(Document):
FILE: ury/ury/doctype/ury_p_and_l_breakup/ury_p_and_l_breakup.py
class URYPandLBreakup (line 7) | class URYPandLBreakup(Document):
FILE: ury/ury/doctype/ury_p_and_l_materials/ury_p_and_l_materials.py
class URYPandLMaterials (line 7) | class URYPandLMaterials(Document):
FILE: ury/ury/doctype/ury_printer_settings/ury_printer_settings.py
class URYPrinterSettings (line 8) | class URYPrinterSettings(Document):
FILE: ury/ury/doctype/ury_production_item_groups/ury_production_item_groups.py
class URYProductionItemGroups (line 7) | class URYProductionItemGroups(Document):
FILE: ury/ury/doctype/ury_production_unit/test_ury_production_unit.py
class TestURYProductionUnit (line 8) | class TestURYProductionUnit(FrappeTestCase):
FILE: ury/ury/doctype/ury_production_unit/ury_production_unit.py
class URYProductionUnit (line 7) | class URYProductionUnit(Document):
FILE: ury/ury/doctype/ury_report_settings/test_ury_report_settings.py
class TestURYReportSettings (line 8) | class TestURYReportSettings(FrappeTestCase):
FILE: ury/ury/doctype/ury_report_settings/ury_report_settings.py
class URYReportSettings (line 7) | class URYReportSettings(Document):
method validate (line 8) | def validate(self):
FILE: ury/ury/doctype/ury_restaurant/test_ury_restaurant.py
class TestURYRestaurant (line 8) | class TestURYRestaurant(FrappeTestCase):
FILE: ury/ury/doctype/ury_restaurant/ury_restaurant.py
class URYRestaurant (line 8) | class URYRestaurant(Document):
FILE: ury/ury/doctype/ury_room/test_ury_room.py
class TestURYRoom (line 8) | class TestURYRoom(FrappeTestCase):
FILE: ury/ury/doctype/ury_room/ury_room.py
class URYRoom (line 8) | class URYRoom(Document):
FILE: ury/ury/doctype/ury_table/test_ury_table.py
class TestURYTable (line 8) | class TestURYTable(FrappeTestCase):
FILE: ury/ury/doctype/ury_table/ury_table.js
method refresh (line 11) | refresh(frm) {
method branch (line 28) | branch(frm) {
FILE: ury/ury/doctype/ury_table/ury_table.py
class URYTable (line 8) | class URYTable(Document):
method autoname (line 9) | def autoname(self):
FILE: ury/ury/doctype/ury_user/ury_user.py
class URYUser (line 8) | class URYUser(Document):
FILE: ury/ury/doctype/ury_variable_expenses/ury_variable_expenses.py
class URYVariableExpenses (line 7) | class URYVariableExpenses(Document):
FILE: ury/ury/hooks/ury_item.py
function validate (line 4) | def validate(doc,method):
function update_menu_item (line 9) | def update_menu_item(doc, event):
function update_variants_add_on (line 14) | def update_variants_add_on(doc, event):
FILE: ury/ury/hooks/ury_pos_closing_entry.py
function before_save (line 3) | def before_save(doc, method):
function validate (line 6) | def validate(doc, method):
function sub_pos_close_check (line 11) | def sub_pos_close_check(doc,method):
function calculate_closing_amount (line 37) | def calculate_closing_amount(doc, method):
function validate_cashier (line 61) | def validate_cashier(doc, method):
FILE: ury/ury/hooks/ury_pos_invoice.py
function before_insert (line 6) | def before_insert(doc, method):
function validate (line 12) | def validate(doc, method):
function before_submit (line 18) | def before_submit(doc, method):
function on_trash (line 24) | def on_trash(doc, method):
function validate_invoice (line 28) | def validate_invoice(doc, method):
function validate_customer (line 77) | def validate_customer(doc, method):
function calculate_and_set_times (line 86) | def calculate_and_set_times(doc, method):
function validate_invoice_print (line 103) | def validate_invoice_print(doc, method):
function table_status_delete (line 114) | def table_status_delete(doc, method):
function pos_invoice_naming (line 123) | def pos_invoice_naming(doc, method):
function order_type_update (line 139) | def order_type_update(doc, method):
function ro_reload_submit (line 153) | def ro_reload_submit(doc, method):
function validate_price_list (line 157) | def validate_price_list(doc, method):
function restrict_existing_order (line 196) | def restrict_existing_order(doc, event):
FILE: ury/ury/hooks/ury_pos_opening_entry.py
function validate (line 5) | def validate(doc,method):
function before_save (line 8) | def before_save(doc, method):
function set_cashier_room (line 13) | def set_cashier_room(doc,method):
function set_current_time (line 30) | def set_current_time(doc,method):
function main_pos_open_check (line 38) | def main_pos_open_check(doc,method):
FILE: ury/ury/hooks/ury_pos_profile.py
function validate (line 5) | def validate(doc, method):
function validate_bill_check (line 10) | def validate_bill_check(doc, method):
function validate_cost_center (line 19) | def validate_cost_center(doc, method):
FILE: ury/ury/hooks/ury_sales_invoice.py
function before_insert (line 4) | def before_insert(doc, method):
function on_update (line 7) | def on_update(doc,method):
function sales_invoice_naming (line 10) | def sales_invoice_naming(doc, method):
function aggregator_unpaid (line 53) | def aggregator_unpaid(doc,method):
function remove_tax (line 58) | def remove_tax(doc,method):
FILE: ury/ury_pos/api.py
function getRestaurantMenu (line 19) | def getRestaurantMenu(pos_profile, room=None, order_type=None):
function getMenuCourses (line 106) | def getMenuCourses():
function getBranch (line 111) | def getBranch():
function getBranchRoom (line 129) | def getBranchRoom():
function getRoom (line 155) | def getRoom():
function getModeOfPayment (line 180) | def getModeOfPayment():
function getInvoiceForCashier (line 193) | def getInvoiceForCashier(status, cashier, limit, limit_start):
function getPosInvoice (line 283) | def getPosInvoice(status, limit, limit_start):
function searchPosInvoice (line 372) | def searchPosInvoice(query,status):
function get_select_field_options (line 401) | def get_select_field_options():
function fav_items (line 410) | def fav_items(customer):
function getCashier (line 431) | def getCashier(room):
function getPosProfile (line 453) | def getPosProfile():
function getPosInvoiceItems (line 561) | def getPosInvoiceItems(invoice):
function posOpening (line 591) | def posOpening():
function getAggregator (line 608) | def getAggregator():
function getAggregatorItem (line 619) | def getAggregatorItem(aggregator):
function getAggregatorMOP (line 646) | def getAggregatorMOP(aggregator):
function create_customer (line 660) | def create_customer(customer_name, mobile_number=None, customer_group="I...
function validate_pos_close (line 699) | def validate_pos_close(pos_profile):
FILE: ury/www/pos.py
function get_context (line 15) | def get_context(context):
function get_context_for_dev (line 48) | def get_context_for_dev():
function get_boot (line 54) | def get_boot():
FILE: urypos/src/stores/Alert.js
method createAlert (line 8) | createAlert(title, message, buttonText) {
FILE: urypos/src/stores/Auth.js
method isAuthenticated (line 41) | isAuthenticated(state) {
method passwordFieldType (line 44) | passwordFieldType() {
method login (line 50) | async login() {
method checkAuthState (line 64) | checkAuthState() {
method getLoginAvatar (line 69) | getLoginAvatar() {
method fetchUserDetails (line 78) | fetchUserDetails() {
method fetchUserRole (line 108) | fetchUserRole() {
method routeToHome (line 156) | routeToHome() {
method isPosOpenChecking (line 161) | isPosOpenChecking() {
method isPosCloseCheck (line 210) | isPosCloseCheck() {
method toggleDropdown (line 230) | toggleDropdown() {
method hideDropdown (line 237) | hideDropdown() {
method logOut (line 241) | logOut() {
FILE: urypos/src/stores/Customer.js
method isFlagSet (line 40) | isFlagSet() {
method pickCustomer (line 45) | async pickCustomer() {
method handleSearchInput (line 79) | handleSearchInput(event) {
method pickCustomerGroup (line 86) | pickCustomerGroup() {
method selectCustomerGroup (line 94) | selectCustomerGroup(group) {
method pickCustomerTerritory (line 98) | pickCustomerTerritory() {
method selectCustomerTerritory (line 106) | selectCustomerTerritory(group) {
method newCustomerData (line 110) | newCustomerData(name) {
method editOrderType (line 120) | editOrderType(orderType) {
method selecetOrderType (line 130) | selecetOrderType(order_type) {
method extractName (line 176) | extractName(content) {
method searchCustomer (line 203) | searchCustomer() {
method selectCustomer (line 212) | async selectCustomer(customer) {
method validateInput (line 229) | validateInput(event) {
method fectchCustomerFavouriteItem (line 242) | async fectchCustomerFavouriteItem() {
FILE: urypos/src/stores/Menu.js
method filteredItems (line 51) | filteredItems(state) {
method totalPages (line 87) | totalPages() {
method paginatedItems (line 90) | paginatedItems() {
method pageNumbers (line 95) | pageNumbers() {
method setColorForBilledInvoice (line 102) | setColorForBilledInvoice() {
method grand_total (line 115) | grand_total() {
method fetchItems (line 124) | fetchItems() {
method pickOrderType (line 168) | pickOrderType() {
method clearPreviousData (line 178) | clearPreviousData() {
method orderTypeSelection (line 201) | orderTypeSelection() {
method handleAggregatorChange (line 252) | handleAggregatorChange() {
method itemNameExtract (line 288) | itemNameExtract(item_name) {
method updateSearchTerm (line 297) | updateSearchTerm() {
method getFullImagePath (line 300) | getFullImagePath(relativePath) {
method handleSearchInput (line 304) | handleSearchInput(event) {
method clearSearch (line 308) | clearSearch(event) {
method showAllItems (line 312) | showAllItems() {
method showSpecialItems (line 319) | showSpecialItems() {
method showModal (line 324) | showModal(item) {
method addToCartAndUpdateQty (line 331) | addToCartAndUpdateQty() {
method getitemQty (line 351) | getitemQty(item) {
method addToCart (line 354) | addToCart(item) {
method incrementItemQuantity (line 367) | incrementItemQuantity(item) {
method decrementItemQuantity (line 392) | decrementItemQuantity(item) {
method removeItemFromCart (line 405) | removeItemFromCart(index) {
FILE: urypos/src/stores/Notification.js
method createNotification (line 6) | createNotification(message) {
FILE: urypos/src/stores/NotificationModal.js
method showModal (line 15) | showModal(options) {
method closeModal (line 26) | closeModal() {
method handleConfirm (line 38) | handleConfirm() {
method handleCancel (line 45) | handleCancel() {
FILE: urypos/src/stores/Table.js
method filteredTables (line 60) | filteredTables(state) {
method takeAway (line 63) | takeAway(state) {
method searchTable (line 66) | searchTable() {
method searchCaptian (line 71) | searchCaptian() {
method toggleTableType (line 78) | toggleTableType(state) {
method tableTypeLabel (line 81) | tableTypeLabel(state) {
method tableTypeClass (line 84) | tableTypeClass(state) {
method fetchRoom (line 89) | fetchRoom() {
method handleRoomChange (line 140) | async handleRoomChange() {
method getCashier (line 148) | getCashier() {
method fetchTable (line 156) | fetchTable() {
method getMenu (line 183) | async getMenu() {
method toggleTableTypeSwitch (line 205) | toggleTableTypeSwitch() {
method tableSearch (line 208) | tableSearch() {
method fetchCaptain (line 221) | fetchCaptain() {
method toggleDropdown (line 232) | async toggleDropdown(index) {
method hideDropdown (line 241) | hideDropdown() {
method selectTable (line 244) | selectTable(tables) {
method selectcaptain (line 248) | selectcaptain(captain) {
method getTimeDifference (line 252) | getTimeDifference(table) {
method getBadgeType (line 275) | getBadgeType(table) {
method getBadgeText (line 291) | getBadgeText(table) {
method addToSelectedTables (line 307) | async addToSelectedTables(table) {
method routeToCart (line 416) | routeToCart(table) {
method routeToMenu (line 420) | routeToMenu(table) {
method invoiceNumberFetching (line 424) | async invoiceNumberFetching() {
FILE: urypos/src/stores/bottomTabs.js
method isLoginPage (line 16) | isLoginPage() {
method currentTab (line 19) | currentTab() {
method checkActiveTable (line 24) | checkActiveTable() {
method clickMenuTab (line 37) | clickMenuTab() {
FILE: urypos/src/stores/invoiceData.js
method fetchInvoiceDetails (line 69) | async fetchInvoiceDetails() {
method invoiceCreation (line 141) | async invoiceCreation() {
method clearDataAfterUpdate (line 359) | clearDataAfterUpdate() {
method billing (line 396) | billing(table) {
method kotReprint (line 426) | kotReprint() {
method updatePrintTable (line 600) | async updatePrintTable(invoiceNo, maxRetries = 3) {
method showCancelInvoiceModal (line 658) | showCancelInvoiceModal() {
FILE: urypos/src/stores/posClosing.js
method isFlagSet (line 42) | isFlagSet() {
method selectPosOpen (line 47) | selectPosOpen() {
method selectPos (line 63) | selectPos(posOpen) {
method getInvoice (line 83) | getInvoice() {
method savePosClosing (line 151) | savePosClosing() {
method getBadgeType (line 211) | getBadgeType() {
method getBadgeText (line 218) | getBadgeText() {
method showSumbitPosCloseModal (line 225) | showSumbitPosCloseModal() {
method sumbitPosClosing (line 228) | sumbitPosClosing() {
method setFormattedDate (line 246) | setFormattedDate() {
method deleteRow (line 253) | deleteRow(index) {
method routeToPosClose (line 256) | routeToPosClose() {
FILE: urypos/src/stores/posOpening.js
method get (line 25) | get() {}
method savePosOpening (line 29) | savePosOpening() {
method getBadgeType (line 68) | getBadgeType() {
method getBadgeText (line 75) | getBadgeText() {
method showSumbitPosOpenModal (line 83) | showSumbitPosOpenModal() {
method sumbitPosOpening (line 86) | sumbitPosOpening() {
method setFormattedDate (line 97) | setFormattedDate() {
method deleteRow (line 103) | deleteRow(index) {
method routeToPosOpen (line 106) | routeToPosOpen() {
FILE: urypos/src/stores/recentOrder.js
method filteredOrders (line 72) | filteredOrders() {
method total (line 77) | total() {
method change (line 83) | change() {
method changeAmount (line 86) | changeAmount() {
method orderNumber (line 90) | orderNumber() {
method totalAmount (line 98) | totalAmount() {
method getPosInvoice (line 104) | async getPosInvoice(selectedStatus, limit, startLimit) {
method handleStatusChange (line 138) | async handleStatusChange() {
method searchPosInvoice (line 150) | async searchPosInvoice(query) {
method handleSearchInput (line 169) | handleSearchInput(event) {
method openPaymentModal (line 176) | openPaymentModal() {
method nextPageClick (line 190) | nextPageClick() {
method previousPageClick (line 196) | previousPageClick() {
method matchesSearchOrder (line 202) | matchesSearchOrder(order) {
method getBadgeType (line 210) | getBadgeType(selectedOrder) {
method getFormattedTime (line 222) | getFormattedTime(postingTime) {
method viewRecentOrder (line 228) | async viewRecentOrder(recentOrder) {
method editOrder (line 265) | async editOrder() {
method showInputBox (line 352) | showInputBox() {
method toggleDiscount (line 357) | toggleDiscount() {
method resetTimer (line 367) | resetTimer() {
method hideInputBox (line 373) | hideInputBox() {
method getModeofPayment (line 377) | getModeofPayment(customer) {
method calculatePaidAmount (line 440) | calculatePaidAmount(paymentMethod) {
method changePaidAmount (line 472) | changePaidAmount(name, value) {
method clearData (line 539) | clearData() {
method showCancelInvoiceModal (line 569) | showCancelInvoiceModal() {
method toggleRecentOrders (line 604) | toggleRecentOrders() {
FILE: urypos/src/stores/utils/PrintWithQz.js
function loadQzPrinter (line 12) | function loadQzPrinter(host){
function disconnectQzPrinter (line 43) | function disconnectQzPrinter(){
function printWithQz (line 48) | function printWithQz(host,htmlToPrint){
Condensed preview — 391 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,840K chars).
[
{
"path": ".github/workflows/ci.yml",
"chars": 2548,
"preview": "\nname: CI\n\non:\n push:\n branches:\n - develop\n pull_request:\n\nconcurrency:\n group: develop-ury-${{ github.event"
},
{
"path": ".gitignore",
"chars": 259,
"preview": ".DS_Store\n*.pyc\n*.egg-info\n*.swp\ntags\nury/docs/current\nnode_modules/\n\nury/public/pos\nury/www/pos.html\nury/public/urypos\n"
},
{
"path": "AGENTS.MD",
"chars": 10634,
"preview": "# URY — Root Agent Documentation\n\n## 1. App Overview\n\n**URY** is a complete restaurant order management system built as "
},
{
"path": "CLAUDE.MD",
"chars": 10,
"preview": "AGENTS.MD\n"
},
{
"path": "FEATURES.md",
"chars": 12202,
"preview": "# Features of URY App\n\nIt's important to note that if no POS Opening entry is created for the day, URY will not allow ta"
},
{
"path": "INSTALLATION.md",
"chars": 1531,
"preview": "# URY Installation\n\nWhile URY may work on existing ERPNext instance, it is recommended that you setup URY on a new frap"
},
{
"path": "LICENSE",
"chars": 34523,
"preview": " GNU AFFERO GENERAL PUBLIC LICENSE\n Version 3, 19 November 2007\n\n Copyright (C)"
},
{
"path": "MANIFEST.in",
"chars": 434,
"preview": "include MANIFEST.in\ninclude requirements.txt\ninclude *.json\ninclude *.md\ninclude *.py\ninclude *.txt\nrecursive-include ur"
},
{
"path": "README.md",
"chars": 5928,
"preview": "\n# URY - Open Source Restaurant Management System\n\nURY is an open source ERP designed to simplify and streamline restaur"
},
{
"path": "SETUP.md",
"chars": 20324,
"preview": "## URY Setup \n\nThis guide takes you step-by-step through setting up URY on top of ERPNext\n\n### Step 1 : Company\n\n- Login"
},
{
"path": "TERMS.md",
"chars": 943,
"preview": "# Terms and Conditions – URY\r\n\r\nBy using **URY** (“the App”), you agree to the following:\r\n\r\n1. **Usage**\r\n\r\n * The Ap"
},
{
"path": "URYMosaic/.gitignore",
"chars": 253,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
},
{
"path": "URYMosaic/.vscode/extensions.json",
"chars": 75,
"preview": "{\n \"recommendations\": [\"Vue.volar\", \"Vue.vscode-typescript-vue-plugin\"]\n}\n"
},
{
"path": "URYMosaic/AGENTS.MD",
"chars": 8559,
"preview": "# URYMosaic — Agent Documentation\n\n## 1. Overview\n\n**URYMosaic** is a Vue 3 real-time Kitchen Order Ticket (KOT) display"
},
{
"path": "URYMosaic/CLAUDE.MD",
"chars": 10,
"preview": "AGENTS.MD\n"
},
{
"path": "URYMosaic/README.md",
"chars": 535,
"preview": "# Vue 3 + Vite\n\nThis template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<scrip"
},
{
"path": "URYMosaic/index.html",
"chars": 410,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <link rel=\"icon\" href=\"/ury.ico\" />\n <meta"
},
{
"path": "URYMosaic/package.json",
"chars": 838,
"preview": "{\n \"name\": \"urymosaic\",\n \"private\": true,\n \"version\": \"0.0.0\",\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite\",\n"
},
{
"path": "URYMosaic/postcss.config.js",
"chars": 80,
"preview": "export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}\n"
},
{
"path": "URYMosaic/proxyOptions.js",
"chars": 374,
"preview": "const common_site_config = require('../../../sites/common_site_config.json');\nconst { webserver_port } = common_site_con"
},
{
"path": "URYMosaic/src/App.vue",
"chars": 269,
"preview": "<template>\n\t<Header/>\n \t<div class=\"bg-slate-300 min-h-screen\"><KOT /></div>\n</template>\n\n<script>\nimport KOT from './co"
},
{
"path": "URYMosaic/src/components/Header.vue",
"chars": 1133,
"preview": "<template>\n\n <header class=\"bg-white p-4 flex justify-between items-center\">\n <div class=\"flex items-center\">\n "
},
{
"path": "URYMosaic/src/components/kot.vue",
"chars": 20177,
"preview": "<template>\n <div class=\"mx-auto p-6 mb-16 relative\">\n <!-- Alert Modal div start-->\n <div\n v-if=\"this.showMo"
},
{
"path": "URYMosaic/src/index.css",
"chars": 60,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\t"
},
{
"path": "URYMosaic/src/main.js",
"chars": 681,
"preview": "import './index.css';\nimport { createApp, reactive } from \"vue\";\nimport App from \"./App.vue\";\n\nimport router from './rou"
},
{
"path": "URYMosaic/src/router/auth.js",
"chars": 194,
"preview": "export default [\n\t{\n\t\tpath: '/login',\n\t\tname: 'Login',\n\t\tcomponent: () =>\n\t\t\timport(/* webpackChunkName: \"login\" */ '../"
},
{
"path": "URYMosaic/src/router/index.js",
"chars": 388,
"preview": "import { createRouter, createWebHistory } from \"vue-router\";\nimport Home from \"../views/Home.vue\";\nimport authRoutes fro"
},
{
"path": "URYMosaic/src/style.css",
"chars": 1413,
"preview": ":root {\n font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n line-height: 1.5;\n font-weight: 400;\n\n"
},
{
"path": "URYMosaic/src/views/Home.vue",
"chars": 336,
"preview": "<template>\n <div>\n\t<h1>Home Page</h1>\n\t<!-- Fetch the resource on click -->\n\t<button @click=\"$resources.ping.fetch()\">P"
},
{
"path": "URYMosaic/src/views/Login.vue",
"chars": 976,
"preview": "<template>\n <div class=\"min-h-screen bg-white flex\">\n\t<div class=\"mx-auto w-full max-w-sm lg:w-96\">\n\t <form @submit.pr"
},
{
"path": "URYMosaic/tailwind.config.js",
"chars": 252,
"preview": "/** @type {import('tailwindcss').Config} */\nexport default {\n content: [\"./src/**/*.{html,jsx,tsx,vue,js,ts}\"],\n theme"
},
{
"path": "URYMosaic/vite.config.js",
"chars": 548,
"preview": "import path from 'path';\nimport { defineConfig } from 'vite';\nimport vue from '@vitejs/plugin-vue';\nimport proxyOptions "
},
{
"path": "package.json",
"chars": 632,
"preview": "{\n \"private\": true,\n \"scripts\": {\n \"ury-pos-install\": \"cd urypos && yarn install --check-files\",\n \"ury-pos-build"
},
{
"path": "pos/.gitignore",
"chars": 253,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
},
{
"path": "pos/AGENTS.MD",
"chars": 13202,
"preview": "# POS Frontend — Agent Documentation\n\n## 1. Overview\n\nThis is the **URY POS v2** — a React 19 single-page application th"
},
{
"path": "pos/CLAUDE.MD",
"chars": 10,
"preview": "AGENTS.MD\n"
},
{
"path": "pos/README.md",
"chars": 1920,
"preview": "# React + TypeScript + Vite\n\nThis template provides a minimal setup to get React working in Vite with HMR and some ESLin"
},
{
"path": "pos/eslint.config.js",
"chars": 734,
"preview": "import js from '@eslint/js'\nimport globals from 'globals'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport reac"
},
{
"path": "pos/index.html",
"chars": 812,
"preview": "<!doctype html>\n{% set user_lang = frappe.lang or 'en' %}\n{% set is_rtl = user_lang in ['ar', 'he', 'fa', 'ur', 'ku'] %}"
},
{
"path": "pos/package.json",
"chars": 1347,
"preview": "{\n \"name\": \"pos\",\n \"private\": true,\n \"version\": \"0.0.0\",\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"b"
},
{
"path": "pos/postcss.config.js",
"chars": 80,
"preview": "export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n} "
},
{
"path": "pos/privateKey.js",
"chars": 56,
"preview": "export const privateKey = `PASTE_YOUR_PRIVATE_KEY_HERE`;"
},
{
"path": "pos/src/App.tsx",
"chars": 1771,
"preview": "import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';\nimport Footer from './components/Footer';\nimp"
},
{
"path": "pos/src/components/AggregatorSelect.tsx",
"chars": 1750,
"preview": "import { useEffect, useState } from 'react';\nimport { usePOSStore } from '../store/pos-store';\nimport { Select, SelectIt"
},
{
"path": "pos/src/components/AuthGuard.tsx",
"chars": 3130,
"preview": "import React, { useEffect, useState } from 'react';\nimport { useRootStore } from '../store/root-store';\nimport { Button "
},
{
"path": "pos/src/components/CommentDialog.tsx",
"chars": 2408,
"preview": "import { useState } from 'react';\nimport { MessageSquare, X } from 'lucide-react';\nimport { Button } from './ui';\nimport"
},
{
"path": "pos/src/components/CustomerSelect.tsx",
"chars": 16863,
"preview": "import { useState, useRef, useEffect } from 'react';\nimport { UserPlus, Mail, Phone, Loader } from 'lucide-react';\nimpor"
},
{
"path": "pos/src/components/Footer.tsx",
"chars": 1236,
"preview": "import { NavLink } from 'react-router-dom';\nimport { \n LayoutGrid, \n ClipboardList, \n Table,\n} from 'lucide-react';\ni"
},
{
"path": "pos/src/components/Header.tsx",
"chars": 7152,
"preview": "import { useState, useEffect, useRef } from 'react';\nimport { t } from '../i18n';\nimport { Link, useLocation } from 'rea"
},
{
"path": "pos/src/components/InitialLoader.tsx",
"chars": 549,
"preview": "import React from 'react';\nimport { Spinner } from './ui/spinner';\nimport { t } from '../i18n';\n\nconst InitialLoader: Re"
},
{
"path": "pos/src/components/LayoutView.tsx",
"chars": 21747,
"preview": "import React, { useState, useRef, useMemo, useEffect, useCallback } from 'react';\nimport { CreditCard as Edit3, Save, Us"
},
{
"path": "pos/src/components/MenuCard.tsx",
"chars": 2683,
"preview": "import { FC } from 'react';\nimport { formatCurrency, cn } from '../lib/utils';\n\ninterface MenuCardProps {\n id: string;\n"
},
{
"path": "pos/src/components/MenuList.tsx",
"chars": 3076,
"preview": "import { useEffect, useMemo } from 'react';\nimport { usePOSStore } from '../store/pos-store';\nimport MenuCard from './Me"
},
{
"path": "pos/src/components/OrderPanel.tsx",
"chars": 12678,
"preview": "import { useState } from 'react';\nimport { Trash2, Edit, FrownIcon, Plus, Loader2, MessageSquare } from 'lucide-react';\n"
},
{
"path": "pos/src/components/OrderStatusSidebar.tsx",
"chars": 2616,
"preview": "import { FileText } from 'lucide-react';\nimport { cn } from '../lib/utils';\nimport { Button } from './ui';\nimport { getO"
},
{
"path": "pos/src/components/OrderTypeSelect.tsx",
"chars": 3519,
"preview": "import { useState } from 'react';\nimport { usePOSStore } from '../store/pos-store';\nimport { useRootStore } from '../sto"
},
{
"path": "pos/src/components/POSOpeningDialog.tsx",
"chars": 2398,
"preview": "import { RefreshCw, AlertTriangle, Monitor } from 'lucide-react';\nimport { Button } from './ui';\nimport { t } from '../i"
},
{
"path": "pos/src/components/POSOpeningProvider.tsx",
"chars": 2737,
"preview": "import { useEffect, useState } from 'react';\nimport { checkPOSOpening, validatePOSClose } from '../lib/pos-opening-api';"
},
{
"path": "pos/src/components/PaymentDialog.tsx",
"chars": 11194,
"preview": "import React, { useState, useEffect } from 'react';\nimport { X, Percent, Coins } from 'lucide-react';\nimport { usePOSSto"
},
{
"path": "pos/src/components/ProductDialog.tsx",
"chars": 17435,
"preview": "import React, { useState, useEffect, useRef, ChangeEvent } from 'react';\nimport { X, Plus, Minus } from 'lucide-react';\n"
},
{
"path": "pos/src/components/ScreenSizeDialog.tsx",
"chars": 2637,
"preview": "import { Monitor, Smartphone, ExternalLink } from 'lucide-react';\nimport { Button } from './ui';\n\nconst ScreenSizeDialog"
},
{
"path": "pos/src/components/ScreenSizeProvider.tsx",
"chars": 967,
"preview": "import { useState, useEffect } from 'react';\nimport ScreenSizeDialog from './ScreenSizeDialog';\n\ninterface ScreenSizePro"
},
{
"path": "pos/src/components/SearchBar.tsx",
"chars": 2573,
"preview": "import React, { useRef, useEffect } from 'react';\nimport { Search, X } from 'lucide-react';\nimport { cn } from '../lib/u"
},
{
"path": "pos/src/components/Sidebar.tsx",
"chars": 4670,
"preview": "import { \n Grid3X3,\n UtensilsCrossed,\n} from 'lucide-react';\nimport { usePOSStore } from '../store/pos-store';\nimport "
},
{
"path": "pos/src/components/Spotlight.tsx",
"chars": 4468,
"preview": "//Not currently used, will update later\nimport { useState, useEffect, useRef } from 'react';\nimport { Search, Command, X"
},
{
"path": "pos/src/components/TableSelectionDialog.tsx",
"chars": 7725,
"preview": "import React, { useEffect, useState } from 'react';\nimport { X, Square, AlertTriangle } from 'lucide-react';\nimport { us"
},
{
"path": "pos/src/components/TableShapeIcon.tsx",
"chars": 552,
"preview": "import { Circle, RectangleHorizontal, Square } from 'lucide-react';\nimport type { Table } from '../lib/table-api';\n\ninte"
},
{
"path": "pos/src/components/ui/README.md",
"chars": 4044,
"preview": "# UI Components\n\nThis directory contains reusable UI components built with class-variance-authority (CVA) for consistent"
},
{
"path": "pos/src/components/ui/badge.tsx",
"chars": 1764,
"preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../"
},
{
"path": "pos/src/components/ui/button.tsx",
"chars": 2116,
"preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../"
},
{
"path": "pos/src/components/ui/card.tsx",
"chars": 2590,
"preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../"
},
{
"path": "pos/src/components/ui/dialog.tsx",
"chars": 4546,
"preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../"
},
{
"path": "pos/src/components/ui/example.tsx",
"chars": 6684,
"preview": "import React, { useState } from 'react';\nimport { Button, Input, Select, Badge, Card, CardHeader, CardTitle, CardDescrip"
},
{
"path": "pos/src/components/ui/index.ts",
"chars": 178,
"preview": "export * from './button';\nexport * from './dialog';\nexport * from './input';\nexport * from './select';\nexport * from './"
},
{
"path": "pos/src/components/ui/input.tsx",
"chars": 1690,
"preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../"
},
{
"path": "pos/src/components/ui/loader.tsx",
"chars": 771,
"preview": "import React from 'react';\nimport { t } from '../../i18n';\n\nconst Loader: React.FC<{ message?: string }> = ({ message })"
},
{
"path": "pos/src/components/ui/select.tsx",
"chars": 3362,
"preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../"
},
{
"path": "pos/src/components/ui/spinner.tsx",
"chars": 714,
"preview": "import { cn } from '../../lib/utils';\nimport { t } from '../../i18n';\n\ninterface SpinnerProps {\n className?: string;\n "
},
{
"path": "pos/src/components/ui/textarea.tsx",
"chars": 774,
"preview": "import * as React from \"react\";\nimport { cn } from \"../../lib/utils\";\n\nexport interface TextareaProps\n extends React.Te"
},
{
"path": "pos/src/components/ui/toast.css",
"chars": 1621,
"preview": "/* Override default toast styles for custom design */\n.Toastify__toast {\n border-radius: 8px !important;\n font-family:"
},
{
"path": "pos/src/components/ui/toast.tsx",
"chars": 1731,
"preview": "import { toast, ToastContainer } from 'react-toastify';\nimport { CheckCircle, XCircle, Info } from 'lucide-react';\nimpor"
},
{
"path": "pos/src/data/doctypes.ts",
"chars": 294,
"preview": "export const DOCTYPES={\n \"POS_PROFILE\": \"POS Profile\",\n \"URY_MENU_COURSE\": \"URY Menu Course\",\n \"URY_ROOM\": \"URY"
},
{
"path": "pos/src/data/menu-data.ts",
"chars": 6127,
"preview": "// Sample menu data\nexport const menuData = [\n // Breakfast Items\n {\n id: 'b1',\n name: 'Plain Dosa',\n price: "
},
{
"path": "pos/src/data/order-types.ts",
"chars": 2353,
"preview": "import { Globe, Phone, ShoppingBag, Truck, Utensils } from \"lucide-react\";\n\nexport type OrderType = \"Dine In\" | \"Take Aw"
},
{
"path": "pos/src/i18n/config.ts",
"chars": 155,
"preview": "export const DEFAULT_LANGUAGE = 'en';\n\nexport const SUPPORTED_LANGUAGES: Record<string, string> = {\n en: 'English',\n f"
},
{
"path": "pos/src/i18n/index.ts",
"chars": 2141,
"preview": "import { loadLocale } from './loader';\nimport { DEFAULT_LANGUAGE } from './config';\nimport { resolveLanguage } from './r"
},
{
"path": "pos/src/i18n/loader.ts",
"chars": 521,
"preview": "import { DEFAULT_LANGUAGE } from './config';\n\ntype TranslationMap = Record<string, unknown>;\n\nconst cache: Record<string"
},
{
"path": "pos/src/i18n/locales/ar.json",
"chars": 9331,
"preview": "{\n \"_meta\": {\n \"language_code\": \"ar\",\n \"translation_method\": \"machine_translated\",\n \"human_verif"
},
{
"path": "pos/src/i18n/locales/en.json",
"chars": 8236,
"preview": "{\n \"common\": {\n \"loading\": \"Loading...\",\n \"cancel\": \"Cancel\",\n \"save\": \"Save\",\n \"apply\": \"Apply\",\n \"chan"
},
{
"path": "pos/src/i18n/locales/fr.json",
"chars": 9435,
"preview": "{\n \"common\": {\n \"loading\": \"Chargement...\",\n \"cancel\": \"Annuler\",\n \"save\": \"Enregistrer\",\n \"apply\": \"Appliq"
},
{
"path": "pos/src/i18n/resolve-language.ts",
"chars": 705,
"preview": "import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from './config';\n\n/**\n * Resolves the active language using the followi"
},
{
"path": "pos/src/index.css",
"chars": 3903,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --f"
},
{
"path": "pos/src/lib/aggregator-api.ts",
"chars": 660,
"preview": "import { call } from './frappe-sdk';\n\nexport interface Aggregator {\n customer: string;\n}\n\nexport interface GetAggregato"
},
{
"path": "pos/src/lib/auth-api.ts",
"chars": 1250,
"preview": "import { call, db, auth } from './frappe-sdk';\n\ntype LoggedUserResponse = string | null;\n\ninterface UserDoc {\n name: st"
},
{
"path": "pos/src/lib/customer-api.ts",
"chars": 2945,
"preview": "import { DOCTYPES } from '../data/doctypes';\nimport { db, call } from './frappe-sdk';\n\nexport interface Customer {\n nam"
},
{
"path": "pos/src/lib/frappe-sdk.ts",
"chars": 213,
"preview": "import { FrappeApp } from \"frappe-js-sdk\";\n\nconst frappe = new FrappeApp(import.meta.env.VITE_FRAPPE_BASE_URL);\n\nexport "
},
{
"path": "pos/src/lib/invoice-api.ts",
"chars": 4163,
"preview": "import { call } from './frappe-sdk';\nimport { OrderType } from '../data/order-types';\n\nexport interface POSInvoice {\n n"
},
{
"path": "pos/src/lib/menu-api.ts",
"chars": 1551,
"preview": "import { call } from './frappe-sdk';\n\nexport interface MenuItem {\n item: string;\n item_name: string;\n item_image: str"
},
{
"path": "pos/src/lib/menu-course-api.ts",
"chars": 360,
"preview": "import { call } from './frappe-sdk';\n\nexport interface MenuCourse {\n name: string;\n label: string;\n}\n\nexport interface"
},
{
"path": "pos/src/lib/order-api.ts",
"chars": 2005,
"preview": "import { call } from './frappe-sdk';\n\nexport interface POSInvoiceItem {\n name: string;\n item_code: string;\n item_name"
},
{
"path": "pos/src/lib/payment-api.ts",
"chars": 826,
"preview": "import { call } from './frappe-sdk';\n\ninterface PaymentMode {\n mode_of_payment: string;\n opening_amount: number;\n}\n\nin"
},
{
"path": "pos/src/lib/pos-opening-api.ts",
"chars": 889,
"preview": "import { call } from './frappe-sdk';\n\nexport interface POSOpeningResponse {\n message: number;\n}\n\nexport interface POSCl"
},
{
"path": "pos/src/lib/pos-profile-api.ts",
"chars": 3881,
"preview": "import { DOCTYPES } from '../data/doctypes';\nimport { call, db } from './frappe-sdk';\n\n// Limited fields response\nexport"
},
{
"path": "pos/src/lib/print-qz.ts",
"chars": 1720,
"preview": "import qz from 'qz-tray';\nimport axios from 'axios';\nimport { privateKey } from '../../privateKey';\nimport { KEYUTIL, KJ"
},
{
"path": "pos/src/lib/print.ts",
"chars": 1420,
"preview": "import { printWithQz } from './print-qz';\nimport {\n getInvoicePrintHtml,\n networkPrint,\n selectNetworkPrinter,\n upda"
},
{
"path": "pos/src/lib/role-utils.ts",
"chars": 680,
"preview": "import type { PosProfileCombined } from './pos-profile-api';\nimport type { User } from '../store/slices/auth-slice';\n\nex"
},
{
"path": "pos/src/lib/storage.ts",
"chars": 536,
"preview": "export const storage = {\n savePosProfileFull: (profile: unknown) => {\n localStorage.setItem('pos_profile', JSON.stri"
},
{
"path": "pos/src/lib/table-api.ts",
"chars": 2190,
"preview": "import { DOCTYPES } from '../data/doctypes';\nimport { db } from './frappe-sdk';\n\nexport interface Room {\n name: string;"
},
{
"path": "pos/src/lib/utils.ts",
"chars": 1264,
"preview": "import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\nimport { storage } from './stora"
},
{
"path": "pos/src/main.tsx",
"chars": 304,
"preview": "import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport './index.css'\nimport App from '."
},
{
"path": "pos/src/pages/Orders.tsx",
"chars": 20984,
"preview": "import React, { useEffect, useRef } from 'react';\nimport { Clock, User, UserCheck, Receipt, Printer, Pencil, X } from 'l"
},
{
"path": "pos/src/pages/POS.tsx",
"chars": 4790,
"preview": "import React, { useState, useRef, useEffect } from 'react';\nimport { t } from '../i18n';\nimport { Star, TrendingUp } fro"
},
{
"path": "pos/src/pages/Table.tsx",
"chars": 15696,
"preview": "import { useCallback, useEffect, useMemo, useState, type MouseEvent } from 'react';\nimport { useNavigate } from 'react-r"
},
{
"path": "pos/src/store/pos-store.ts",
"chars": 20918,
"preview": "import { create } from 'zustand';\nimport { v4 as uuidv4 } from 'uuid';\nimport { storage } from '../lib/storage';\nimport "
},
{
"path": "pos/src/store/root-store.ts",
"chars": 475,
"preview": "import { create } from 'zustand';\nimport { createAuthSlice, AuthSlice } from './slices/auth-slice';\nimport { createConfi"
},
{
"path": "pos/src/store/slices/auth-slice.ts",
"chars": 1595,
"preview": "import { StateCreator } from 'zustand';\nimport { getLoggedUser, getUserRoles } from '../../lib/auth-api';\n\nexport interf"
},
{
"path": "pos/src/store/slices/config-slice.ts",
"chars": 3065,
"preview": "import { StateCreator } from 'zustand';\nimport { AuthSlice } from './auth-slice';\nimport { getCombinedPosProfile, PosPro"
},
{
"path": "pos/src/store/slices/orders-slice.ts",
"chars": 5817,
"preview": "import { StateCreator } from 'zustand';\nimport { OrderType } from '../../data/order-types';\nimport { call } from '../../"
},
{
"path": "pos/src/vite-env.d.ts",
"chars": 38,
"preview": "/// <reference types=\"vite/client\" />\n"
},
{
"path": "pos/tailwind.config.js",
"chars": 3181,
"preview": "/** @type {import('tailwindcss').Config} */\nexport default {\n content: [\n \"./index.html\",\n \"./src/**/*.{js,ts,jsx"
},
{
"path": "pos/tsconfig.app.json",
"chars": 665,
"preview": "{\n \"compilerOptions\": {\n \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n \"target\": \"ES2020\",\n"
},
{
"path": "pos/tsconfig.json",
"chars": 119,
"preview": "{\n \"files\": [],\n \"references\": [\n { \"path\": \"./tsconfig.app.json\" },\n { \"path\": \"./tsconfig.node.json\" }\n ]\n}\n"
},
{
"path": "pos/tsconfig.node.json",
"chars": 613,
"preview": "{\n \"compilerOptions\": {\n \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n \"target\": \"ES2022\","
},
{
"path": "pos/vite.config.ts",
"chars": 476,
"preview": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\nimport { fileURLToPath } from 'url'\nimport "
},
{
"path": "pyproject.toml",
"chars": 1486,
"preview": "[project]\nname = \"ury\"\nauthors = [\n { name = \"Tridz Technologies\", email = \"info@tridz.com\"}\n]\ndescription = \"A Compl"
},
{
"path": "requirements.txt",
"chars": 106,
"preview": "# frappe -- https://github.com/frappe/frappe is installed via 'bench init'\n# Node version minimum 18.18.0 "
},
{
"path": "setup.py",
"chars": 500,
"preview": "from setuptools import setup, find_packages\n\nwith open(\"requirements.txt\") as f:\n\tinstall_requires = f.read().strip().sp"
},
{
"path": "ury/__init__.py",
"chars": 22,
"preview": "__version__ = \"0.2.1\"\n"
},
{
"path": "ury/config/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/config/desktop.py",
"chars": 126,
"preview": "from frappe import _\n\ndef get_data():\n\treturn [\n\t\t{\n\t\t\t\"module_name\": \"URY\",\n\t\t\t\"type\": \"module\",\n\t\t\t\"label\": _(\"URY\")\n\t"
},
{
"path": "ury/config/docs.py",
"chars": 248,
"preview": "\"\"\"\nConfiguration for docs\n\"\"\"\n\n# source_link = \"https://github.com/[org_name]/ury\"\n# headline = \"App that does everythi"
},
{
"path": "ury/fixtures/client_script.json",
"chars": 3662,
"preview": "[\n {\n \"docstatus\": 0,\n \"doctype\": \"Client Script\",\n \"dt\": \"POS Invoice\",\n \"enabled\": 0,\n \"modified\": \"2023-12-19 15"
},
{
"path": "ury/fixtures/custom_field.json",
"chars": 130978,
"preview": "[\n {\n \"allow_in_quick_entry\": 0,\n \"allow_on_submit\": 0,\n \"bold\": 0,\n \"collapsible\": 0,\n \"collapsible_depends_on\": n"
},
{
"path": "ury/fixtures/custom_html_block.json",
"chars": 830,
"preview": "\n[\n {\n \"docstatus\": 0,\n \"doctype\": \"Custom HTML Block\",\n \"html\":\"<a href=\\\"/pos\\\">\\n <img class=\\\"uryp"
},
{
"path": "ury/fixtures/property_setter.json",
"chars": 448,
"preview": "[\n {\n \"default_value\": null,\n \"doc_type\": \"POS Closing Entry Detail\",\n \"docstatus\": 0,\n \"doctype\": \"Property Setter\""
},
{
"path": "ury/fixtures/role.json",
"chars": 1307,
"preview": "[\n {\n \"bulk_actions\": 1,\n \"dashboard\": 1,\n \"desk_access\": 1,\n \"disabled\": 0,\n \"docstatus\": 0,\n \"doctype\": \"Role\",\n"
},
{
"path": "ury/hooks.py",
"chars": 12771,
"preview": "from . import __version__ as app_version\n\napp_name = \"ury\"\napp_title = \"URY\"\napp_publisher = \"Tridz Technologies Pvt. Lt"
},
{
"path": "ury/install.py",
"chars": 261,
"preview": "import click\n\nfrom ury.setup import after_install as setup\n\n\ndef after_install():\n try:\n print(\"Setting up URY"
},
{
"path": "ury/modules.txt",
"chars": 3,
"preview": "URY"
},
{
"path": "ury/patches/v2_0/default_permissions.py",
"chars": 11426,
"preview": "import frappe\nfrom frappe.permissions import add_permission, update_permission_property\nfrom frappe.core.doctype.doctype"
},
{
"path": "ury/patches.txt",
"chars": 227,
"preview": "[pre_model_sync]\n# Patches added in this section will be executed before doctypes are migrated\n\n[post_model_sync]\n# Patc"
},
{
"path": "ury/permission.py",
"chars": 144,
"preview": "import frappe\n\ndef check_app_permission():\n\tif frappe.session.user == \"Administrator\" or frappe.session.user == \"System "
},
{
"path": "ury/public/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "ury/public/js/jsrsasign-all-min.js",
"chars": 303180,
"preview": "/*\n * jsrsasign(all) 8.0.4 (2017-09-14) (c) 2010-2017 Kenji Urushima | kjur.github.com/jsrsasign/license\n */\n\n/*!\nCopyri"
},
{
"path": "ury/public/js/pos_extend.js",
"chars": 13342,
"preview": "frappe.provide(\"erpnext.PointOfSale\");\nfrappe.pages[\"point-of-sale\"].on_page_load = function (wrapper) {\n frappe.ui.mak"
},
{
"path": "ury/public/js/pos_print.js",
"chars": 9147,
"preview": "frappe.require([\n '/assets/ury/js/qz-tray.js',\n '/assets/ury/js/jsrsasign-all-min.js',\n '/assets/ury/js/sign-me"
},
{
"path": "ury/public/js/quick_entry.js",
"chars": 1133,
"preview": "frappe.provide('frappe.ui.form');\nfrappe.ui.form.CustomerQuickEntryForm = class POSQuickEntryForm extends frappe.ui.form"
},
{
"path": "ury/public/js/qz-tray.js",
"chars": 137850,
"preview": "'use strict';\n\n/**\n * @version 2.2.3-SNAPSHOT\n * @overview QZ Tray Connector\n * <p/>\n * Connects a web client to the QZ "
},
{
"path": "ury/public/js/restrict_qty_edit_pos.js",
"chars": 2989,
"preview": "frappe.ui.form.on('POS Invoice', {\n async onload(frm) {\n const fieldValue = await frappe.db.get_value('POS Pro"
},
{
"path": "ury/public/js/sign-message.js",
"chars": 3578,
"preview": "/*\n * JavaScript client-side example using jsrsasign\n */\n\n// #########################################################\n/"
},
{
"path": "ury/public/js/ury_pos_kot.js",
"chars": 1188,
"preview": "let old_items = [];\nlet new_items = [];\nlet finalarray = [];\nfrappe.ui.form.on(\"POS Invoice\", {\n refresh: function (frm"
},
{
"path": "ury/setup.py",
"chars": 8676,
"preview": "import frappe\nimport os\nimport click\nfrom frappe import _\n\nfrom frappe.custom.doctype.custom_field.custom_field import c"
},
{
"path": "ury/templates/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/templates/pages/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/uninstall.py",
"chars": 516,
"preview": "import click\nimport frappe\n\nfrom ury.setup import before_uninstall as remove_custom_fields\n\n\ndef before_uninstall():\n "
},
{
"path": "ury/ury/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/api/button_permission.py",
"chars": 245,
"preview": "import frappe\n\n\n@frappe.whitelist()\ndef cancel_check():\n if frappe.permissions.has_permission(\n \"POS Invoice\","
},
{
"path": "ury/ury/api/pos_extend.py",
"chars": 3901,
"preview": "import frappe\nfrom frappe import _\n\ndef validate_search_input(search_term):\n \"\"\"Validate and sanitize search input\"\"\""
},
{
"path": "ury/ury/api/ury_kot_display.py",
"chars": 4142,
"preview": "import json\n\nimport frappe\nfrom ury.ury_pos.api import getBranch\nfrom frappe.utils import get_datetime\n\n\n# Function to s"
},
{
"path": "ury/ury/api/ury_kot_generate.py",
"chars": 13314,
"preview": "import json\n\nimport frappe\nfrom ury.ury_pos.api import getBranch\n\n\n# Load JSON data or return as is if it's already a Py"
},
{
"path": "ury/ury/api/ury_kot_notification.py",
"chars": 2210,
"preview": "import frappe\n\n\ndef get_users_with_role(role_name):\n users_with_role = frappe.get_all(\n \"Has Role\", filters={\""
},
{
"path": "ury/ury/api/ury_kot_order_number.py",
"chars": 3421,
"preview": "import frappe\n\n\ndef set_order_number(doc, event):\n pos_profile = doc.pos_profile\n if doc.order_type == \"Aggregator"
},
{
"path": "ury/ury/api/ury_kot_reprint.py",
"chars": 1747,
"preview": "import frappe\nfrom frappe.utils import cint\nfrom frappe.utils.print_format import print_by_server\n\n\n\n@frappe.whitelist()"
},
{
"path": "ury/ury/api/ury_kot_validation.py",
"chars": 4126,
"preview": "import frappe\nimport json\n\nfrom frappe.utils import get_datetime, datetime\n\n\ndef kotValidationThread():\n current_date"
},
{
"path": "ury/ury/api/ury_menu_course_validation.py",
"chars": 513,
"preview": "import frappe\n\ndef validate_priority(doc,event):\n # Check if the selected priority is already used by another course\n"
},
{
"path": "ury/ury/api/ury_print.py",
"chars": 6206,
"preview": "import frappe\nfrom frappe import _\n\nimport os\n\nfrom pypdf import PdfWriter\n\nno_cache = 1\n\nbase_template_path = \"www/prin"
},
{
"path": "ury/ury/custom/item.json",
"chars": 11927,
"preview": "{\n \"custom_fields\": [\n {\n \"_assign\": null,\n \"_comments\": null,\n \"_liked_by\": null,\n \"_user_tags\": null,\n \"all"
},
{
"path": "ury/ury/doctype/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/doctype/aggregator_settings/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/doctype/aggregator_settings/aggregator_settings.json",
"chars": 1054,
"preview": "{\n \"actions\": [],\n \"allow_rename\": 1,\n \"creation\": \"2024-08-13 12:50:02.398001\",\n \"doctype\": \"DocType\",\n \"editable_grid\""
},
{
"path": "ury/ury/doctype/aggregator_settings/aggregator_settings.py",
"chars": 220,
"preview": "# Copyright (c) 2024, Tridz Technologies Pvt. Ltd and contributors\n# For license information, please see license.txt\n\n# "
},
{
"path": "ury/ury/doctype/item_add_on/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/doctype/item_add_on/item_add_on.json",
"chars": 674,
"preview": "{\n \"actions\": [],\n \"allow_rename\": 1,\n \"creation\": \"2025-07-25 18:42:32.997931\",\n \"doctype\": \"DocType\",\n \"editable_grid\""
},
{
"path": "ury/ury/doctype/item_add_on/item_add_on.py",
"chars": 212,
"preview": "# Copyright (c) 2025, Tridz Technologies Pvt. Ltd and contributors\n# For license information, please see license.txt\n\n# "
},
{
"path": "ury/ury/doctype/menu_for_room/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/doctype/menu_for_room/menu_for_room.js",
"chars": 197,
"preview": "// Copyright (c) 2024, Tridz Technologies Pvt. Ltd and contributors\n// For license information, please see license.txt\n\n"
},
{
"path": "ury/ury/doctype/menu_for_room/menu_for_room.json",
"chars": 771,
"preview": "{\n \"actions\": [],\n \"allow_rename\": 1,\n \"creation\": \"2024-01-07 13:11:04.784852\",\n \"default_view\": \"List\",\n \"doctype\": \"D"
},
{
"path": "ury/ury/doctype/menu_for_room/menu_for_room.py",
"chars": 213,
"preview": "# Copyright (c) 2024, Tridz Technologies Pvt. Ltd and contributors\n# For license information, please see license.txt\n\n# "
},
{
"path": "ury/ury/doctype/menu_for_room/test_menu_for_room.py",
"chars": 195,
"preview": "# Copyright (c) 2024, Tridz Technologies Pvt. Ltd and Contributors\n# See license.txt\n\n# import frappe\nfrom frappe.tests."
},
{
"path": "ury/ury/doctype/multiple_rooms/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/doctype/multiple_rooms/multiple_rooms.json",
"chars": 638,
"preview": "{\n \"actions\": [],\n \"allow_rename\": 1,\n \"creation\": \"2025-03-25 13:48:29.430246\",\n \"default_view\": \"List\",\n \"doctype\": \"D"
},
{
"path": "ury/ury/doctype/multiple_rooms/multiple_rooms.py",
"chars": 215,
"preview": "# Copyright (c) 2025, Tridz Technologies Pvt. Ltd and contributors\n# For license information, please see license.txt\n\n# "
},
{
"path": "ury/ury/doctype/order_type_menu/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/doctype/order_type_menu/order_type_menu.json",
"chars": 850,
"preview": "{\n \"actions\": [],\n \"allow_rename\": 1,\n \"creation\": \"2025-03-06 10:26:48.242529\",\n \"default_view\": \"List\",\n \"doctype\": \"D"
},
{
"path": "ury/ury/doctype/order_type_menu/order_type_menu.py",
"chars": 215,
"preview": "# Copyright (c) 2025, Tridz Technologies Pvt. Ltd and contributors\n# For license information, please see license.txt\n\n# "
},
{
"path": "ury/ury/doctype/pos_item_variants/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/doctype/pos_item_variants/pos_item_variants.json",
"chars": 680,
"preview": "{\n \"actions\": [],\n \"allow_rename\": 1,\n \"creation\": \"2025-07-25 18:52:38.763971\",\n \"doctype\": \"DocType\",\n \"editable_grid\""
},
{
"path": "ury/ury/doctype/pos_item_variants/pos_item_variants.py",
"chars": 218,
"preview": "# Copyright (c) 2025, Tridz Technologies Pvt. Ltd and contributors\n# For license information, please see license.txt\n\n# "
},
{
"path": "ury/ury/doctype/role_permitted/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/doctype/role_permitted/role_permitted.json",
"chars": 609,
"preview": "{\n \"actions\": [],\n \"allow_rename\": 1,\n \"creation\": \"2024-01-04 11:00:27.812098\",\n \"doctype\": \"DocType\",\n \"editable_grid\""
},
{
"path": "ury/ury/doctype/role_permitted/role_permitted.py",
"chars": 215,
"preview": "# Copyright (c) 2024, Tridz Technologies Pvt. Ltd and contributors\n# For license information, please see license.txt\n\n# "
},
{
"path": "ury/ury/doctype/sub_pos_closing/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/doctype/sub_pos_closing/sub_pos_closing.js",
"chars": 6284,
"preview": "// Copyright (c) 2025, Tridz Technologies Pvt. Ltd and contributors\n// For license information, please see license.txt\n\n"
},
{
"path": "ury/ury/doctype/sub_pos_closing/sub_pos_closing.json",
"chars": 4787,
"preview": "{\n \"actions\": [],\n \"allow_rename\": 1,\n \"autoname\": \"SUB-CLO-.YYYY.-.#####\",\n \"creation\": \"2025-03-26 03:40:02.290820\",\n "
},
{
"path": "ury/ury/doctype/sub_pos_closing/sub_pos_closing.py",
"chars": 4184,
"preview": "\nimport frappe\nfrom frappe import _\nfrom frappe.utils import flt, get_datetime\nfrom datetime import datetime, timedelta\n"
},
{
"path": "ury/ury/doctype/sub_pos_closing/test_sub_pos_closing.py",
"chars": 197,
"preview": "# Copyright (c) 2025, Tridz Technologies Pvt. Ltd and Contributors\n# See license.txt\n\n# import frappe\nfrom frappe.tests."
},
{
"path": "ury/ury/doctype/sub_pos_closing_payment/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "ury/ury/doctype/sub_pos_closing_payment/sub_pos_closing_payment.json",
"chars": 1380,
"preview": "{\n \"actions\": [],\n \"allow_rename\": 1,\n \"creation\": \"2025-03-26 03:38:51.845091\",\n \"default_view\": \"List\",\n \"doctype\": \"D"
},
{
"path": "ury/ury/doctype/sub_pos_closing_payment/sub_pos_closing_payment.py",
"chars": 222,
"preview": "# Copyright (c) 2025, Tridz Technologies Pvt. Ltd and contributors\n# For license information, please see license.txt\n\n# "
},
{
"path": "ury/ury/doctype/sub_pos_invoices/__init__.py",
"chars": 0,
"preview": ""
}
]
// ... and 191 more files (download for full content)
About this extraction
This page contains the full source code of the ury-erp/ury GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 391 files (1.6 MB), approximately 488.5k tokens, and a symbol index with 769 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.