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/`** — 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/", "to_route": "pos"}, {"from_route": "/URYMosaic/", "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/` | | 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/.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//`. → 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_/`. → 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/` - **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. > :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. 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. Copyright (C) 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 . 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 . ================================================ 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. > :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.
View Role Permissions | 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 | ✓ | ✓ | ✓ | ✓ | - | - | - | - | - | - | - | - | - | - |
### 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.
Branch
- 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.
URY Restaurant
- **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.
URY Room
- **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.
URY Menu
- **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:
URY Menu Course
### 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.
URY Table
- **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.

POS Profile QZ

- **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.

POS Profile QZ

- **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.

POS Profile Restrictions

- **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.

POS Profile KOT Settings

- **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.
URY Production Unit
- **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.

Network Printer Settings

### 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
Sub POS Closing
- 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/` (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 ` ================================================ 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 ================================================ ================================================ FILE: URYMosaic/src/components/Header.vue ================================================ ================================================ FILE: URYMosaic/src/components/kot.vue ================================================ ================================================ 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 ================================================ ================================================ FILE: URYMosaic/src/views/Login.vue ================================================ ================================================ 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/.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: `useStore` hooks, state slices in `store/slices/`. - API wrappers: `lib/-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/` 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 ================================================ {% set user_lang = frappe.lang or 'en' %} {% set is_rtl = user_lang in ['ar', 'he', 'fa', 'ur', 'ku'] %} URY POS
================================================ 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 ( <>
} /> } /> } />
); } 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([]); 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 (
); } ================================================ 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 = ({ 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 (
); } if (authError || configError) { return (
⚠️

Access Denied

{authError || configError}

); } if (!user) { // The checkAuth function will handle the redirect to login return null; } if (!posProfile) { return (
⚠️

Configuration Error

POS Profile not found or not configured.

); } if (!hasAccess) { return (
🔒

Permission Required

You do not have permission to access this application.

Required roles: {posProfile.role_allowed_for_billing.map(r => r.role).join(', ')}

); } 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 (

{t('comment.title')}