# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview A Frappe/ERPNext app that replaces the standard "Add Multiple" button in sales documents (currently Sales Invoice) with a custom item selection dialog inspired by 1C-style item selection. The dialog provides enhanced filtering, real-time stock visibility, pricing, and a shopping cart interface. ## Development Commands All `bench` commands run from `/home/frappe/frappe-bench`: ```bash bench install-app selections # Install/reinstall bench start # Development server bench console # Python REPL with Frappe context bench clear-cache # Required after JS/CSS/hook changes bench migrate # Apply DB schema changes bench --site [sitename] tail-logs # View error logs ``` ### Code Quality Pre-commit hooks (ruff, eslint, prettier, pyupgrade) — install once: ```bash cd apps/selections && pre-commit install ``` Manual linting (from app directory): ```bash ruff check . && ruff format . npx eslint selections/public/js/**/*.js npx prettier --write selections/public/js/**/*.js ``` Ruff: line length 110, tab indentation, import sorting enabled. See `pyproject.toml` for ignored rules. ## Architecture ### Key Components **Backend API (`selections/api.py`)**: - `get_items_for_selection(filters, start, page_length)`: Main endpoint. Fetches items with stock via LEFT JOIN on `Bin`, then enriches with prices per-item in Python. - `get_item_groups_tree()`: Returns hierarchical item group tree for filtering. Cached in `frappe.cache()` for 5 minutes. - `get_stock_across_warehouses(item_code)`: Returns per-warehouse stock for a single item (used for stock tooltip in dialog). - `add_items_to_document(doctype, docname, items)`: Saves items server-side. Currently unused — the preferred flow adds items directly via JS. **Frontend Dialog (`selections/public/js/selection_dialog.js`)**: - `SelectionDialog` class: three-column layout (filters, items grid, cart). Exposed as `window.SelectionDialog`. - Loaded globally via `app_include_js` in `hooks.py` — available on all desk pages. **DocType Integration (`selections/public/js/sales_invoice.js`)**: - Loaded per-doctype via `doctype_js` in `hooks.py`. - On `refresh`, removes `.grid-add-multiple-rows` (Frappe's standard button) and injects a replacement button using `insertAfter` + `e.stopImmediatePropagation()` to prevent Frappe's own click handler from firing on the shared CSS class. ### Data Flow 1. User clicks "Add Multiple" in Sales Invoice → `SelectionDialog` instantiated 2. Dialog fetches warehouses, price lists, item groups via API 3. User filters → `get_items_for_selection()` queries DB and returns paginated results 4. Cart state managed client-side 5. Finalize → items added to form via `frappe.model.add_child()`, then `frm.doc.set_missing_values()` fills taxes/prices ### Price Lookup Chain `get_price_data()` in `api.py` attempts pricing in this order: 1. Customer-specific `Item Price` record 2. General `Item Price` record (no customer) 3. ERPNext's `get_item_details()` for pricing rules/discounts (falls back gracefully on exception) ### Sorting Behavior - Sort by `item_name` or `available_qty`: done at SQL level (works correctly across pages) - Sort by `rate`: done in Python on the current page only — price data is fetched per-item after the SQL query ### Frappe Framework Patterns **Whitelisted Methods**: `@frappe.whitelist()` required for any method callable from the client. **Query Builder** (not raw SQL): ```python Item = frappe.qb.DocType('Item') query = frappe.qb.from_(Item).select(Item.name).where(Item.disabled == 0) ``` **Child Table Operations**: ```javascript const row = frappe.model.add_child(frm.doc, 'Sales Invoice Item', 'items'); row.item_code = 'ITEM-001'; frm.refresh_field('items'); ``` **Form Scripting**: ```javascript frappe.ui.form.on('DocType Name', { refresh: function(frm) { /* ... */ } }); ``` ## Important Conventions ### Python - Tabs for indentation (ruff-enforced) - `from frappe.utils import flt, cstr` for numeric/string helpers - `frappe.throw(_('Message'))` for user-facing errors; wrap all user strings with `_()` - API docstrings are in Russian (existing codebase convention) ### JavaScript - Frappe globals available: `frappe`, `__()`, `flt()`, `format_currency()`, `$` (jQuery) - `frappe.show_alert()` for transient notifications; `frappe.msgprint()` for blocking messages ## Extending to Other DocTypes 1. Add to `doctype_js` in `hooks.py`: ```python doctype_js = { "Sales Invoice": "public/js/sales_invoice.js", "Sales Order": "public/js/sales_order.js" } ``` 2. Create the JS file following the pattern in `sales_invoice.js` 3. Pass the correct child table name to `SelectionDialog` (e.g., `'Sales Order Item'`) ## After Making Changes ```bash bench clear-cache # Always required for JS/CSS/hook changes # Then hard-refresh browser (Ctrl+Shift+R) ```