selections/CLAUDE.md

4.9 KiB

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:

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:

cd apps/selections && pre-commit install

Manual linting (from app directory):

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):

Item = frappe.qb.DocType('Item')
query = frappe.qb.from_(Item).select(Item.name).where(Item.disabled == 0)

Child Table Operations:

const row = frappe.model.add_child(frm.doc, 'Sales Invoice Item', 'items');
row.item_code = 'ITEM-001';
frm.refresh_field('items');

Form Scripting:

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:
doctype_js = {
    "Sales Invoice": "public/js/sales_invoice.js",
    "Sales Order": "public/js/sales_order.js"
}
  1. Create the JS file following the pattern in sales_invoice.js
  2. Pass the correct child table name to SelectionDialog (e.g., 'Sales Order Item')

After Making Changes

bench clear-cache   # Always required for JS/CSS/hook changes
# Then hard-refresh browser (Ctrl+Shift+R)