132 lines
4.9 KiB
Markdown
132 lines
4.9 KiB
Markdown
# 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)
|
|
```
|