Purchase Invoice Item.tax_type (Tax Free / Taxable) models the 5%
withholding that only applies to a Purchase Act. EQF and Import documents
don't use it, but the e-taxes import now stamps purchase_type=1 on EQF
invoices too, which made tax_type mandatory on every line.
Narrow mandatory_depends_on to act_type == "Purchase Act".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Patch the certificate Table MultiSelect dropdown to drop the leading document
id from the description, leaving cert_no + taxpayer name / VÖEN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add vat_offset (allow_on_submit) and vat_offset_line_code (308/314/316,
shown when vat_offset is set, allow_on_submit) to Purchase Order and
Purchase Invoice. Relabel the existing line_code to "YGB Line Code" to
distinguish the two independent line codes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add etaxes_invoice_series/number (read-only) and etaxes_invoice_comment_2 on
Purchase Invoice and Purchase Order, plus a general-purpose remarks field on
Purchase Order (comment 1) placed at the same spot as the standard PI remarks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the agricultural_goods checkbox to Purchase Order and make its act_type
/ agricultural_country / ygb / line_code respond to it, mirroring Purchase
Invoice. act_type is now mandatory when purchase_type or agricultural_goods
is set (Purchase Invoice and Purchase Order). The flag carries over to
Purchase Invoice via the standard PO->PI mapping (same fieldname).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sales Invoice and Sales Order keep only the Agricultural Goods checkbox;
remove purchase_type, act_type and agricultural_country (purchase-side
fields that made no sense on sales documents). Also remove the now-pointless
sales_classification.js (purchase_type <-> agricultural_goods mutual
exclusion) and its hook registration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add depends_on and mandatory_depends_on on the Purchase Invoice act_kind
field so it is shown and mandatory only when act_type == "Purchase Act".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Place the Item agricultural_goods checkbox next to the other Item flags
(after is_fixed_asset) on the Details tab instead of among the link
fields.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an item-level agricultural_goods flag so a document marked agricultural
can still contain non-agricultural items; tax reports filter on it.
Also fixes a duplicate "Item" key in the custom_fields dict (the second
entry shadowed the first), by merging both Item blocks into one so all
Item custom fields are created on a fresh install.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every app_include_js entry is appended to the desk `Link: rel=preload`
header; this deployment's edge proxy has a ~4 KB header buffer and
overflowing it returns 502 (see jey_layout/hooks.py). jey_erp was the
biggest contributor with 9 desk-wide scripts, 7 of which are form-scoped
and were loading on every desk page for nothing (asset.js alone is 1143
lines).
- Move vat_calculator/sales_invoice_vat/tax_articles/certificates → Sales
Invoice doctype_js; vat_calculator/sales_order_vat → Sales Order; asset
→ Asset. doctype_js is inlined into form meta server-side, never touching
the preload header. Order preserved (vat_calculator first — *_vat.js call
jey_erp.vat_calculator.init at load time).
- Delete public/js/sales_invoice.js: empty (0 lines) and already a dup
doctype_js entry.
- app_include_js: 9 → 2 (link_field_fix.js + bank_excel_import.bundle.js).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
app_include assets are appended to the desk's `Link: rel=preload` response
header. This deployment sits behind an edge proxy with a 4 KB header buffer;
the extra entry tipped the header over the limit → 502 "upstream sent too
big header" on every desk request (only at the edge proxy, so it was
invisible to local nginx/gunicorn curl checks).
Move the checkbox mutual-exclusion script to doctype_js for Sales Invoice
and Sales Order — loaded per-form, never added to the preload header. Same
mechanism already documented in jey_layout/hooks.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the Purchase Invoice behaviour on the sales side. Add the
`agricultural_goods` Check next to `purchase_type`; the two are mutually
exclusive (new sales_classification.js clears the other when one is set).
Agricultural Goods reveals the same fields as Purchase Type: act_type and
agricultural_country, with depends_on widened to
`purchase_type || agricultural_goods`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clear depends_on on the Purchase Invoice Item tax_type and
purchase_tax_amount custom fields so their grid columns are always
shown (in_list_view). Mandatory state still toggles via
mandatory_depends_on on tax_type. Previously depends_on hid the column
and a JS hidden-toggle re-added it, which broke the grid layout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regenerated on every migrate (after_migrate hook), so its churn caused
'local changes would be overwritten' on git pull across machines.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-add the `agricultural_goods` Check field (was a manual, unversioned DB
custom field) next to `purchase_type`. The two checkboxes are mutually
exclusive (new purchase_invoice.js clears the other when one is set).
Agricultural Goods now reveals the same fields as Purchase Type:
act_type / agricultural_country / ygb / line_code on the parent, plus
tax_type and purchase_tax_amount on the items — all depends_on widened to
`purchase_type || agricultural_goods`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add YQB (Int, length 12) and Line Code (Select 310/311/314/316) custom
fields after Country on Purchase Order and Purchase Invoice, both shown
only when purchase_type is checked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a "Sertifikatın seriyə / nömrəsi" multi-select cell to Sales Invoice
Item, mirroring the existing tax-article cell:
- new child DocType "Sales Invoice Certificate" (parent_row + certificate
-> E-Taxes Presented Certificate), parent table custom_item_certificates,
per-row display field custom_certificates_display; reuses the existing
custom_si_row_key to link rows to certificates.
- sales_invoice_certificates.js: clicking an empty cell first asks
"0% dərəcəsi ilə ƏDV-yə cəlb olunma haqqında sertifikatlar var?" (yes ->
open the Table MultiSelect picker, no -> open nothing); rows that already
have certificates open the picker directly.
- server hook sync_item_certificates: stable row keys, drop orphans,
refresh the display label (not bound to the tax template).
- patch add_certificates_to_si_grid_views: inject the new column into saved
per-user grid layouts.
- lock the tax-article cell (read-only) for any line that has a certificate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add read-only custom fields etaxes_classification_code (Təsnifat Kodu)
and etaxes_explanation on Journal Entry for E-Taxes VAT imports, with
matching translation markers.
Move asset-category creation logging from frappe.log_error (which spammed
the Error Log DocType) to a file logger; keep log_error only for real
creation failures. Drop the manual frappe.db.commit inside the
Company.on_update path so the framework keeps the save atomic.
Reuse the realtime progress-bar race fix in currency_exchange_list.js.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add set_default_settings() to the setup_wizard_complete hook so fresh
installs get sane defaults after company creation:
- Global Defaults.disable_rounded_total = 1
- Accounts Settings.delete_linked_ledger_entries = 1
Uses get_single().save() (not db.set_value) so each doctype's
on_update/validate side effects run — disable_rounded_total in particular
is read via frappe.db.get_default, only populated in on_update. Idempotent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the unused Payment Entry doctype_js (payment_entry_mod.js) and its
hook. Rename the boot_session entrypoint to apply_vat_boot_patches and drop
the obsolete party_account_types injection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- print_format.json: export Sales Invoice / Delivery Note / Purchase Order
and related Jinja print formats
- translation_markers.py: drop the old tax_article marker, add the deprecated
single-field description marker
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Payment Entry: add the two new Əlavə 3 groups (ƏDV tutulan dövriyyə /
ƏDV tutulan əməliyyat sayılmayan dövriyyə) to the tax_article link filter
- Sales Invoice: allow tax articles on ƏDV 18% lines, filtered to
"ƏDV tutulan dövriyyə barədə məlumat" (301.x). ƏDV daxil 18% stays excluded
(its prefix doesn't match "ƏDV 18%").
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
It is no longer auto-synced (mirror was removed); mark it deprecated and point
to the custom_item_tax_articles multi-select.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Insert custom_tax_articles_display into saved layouts independently instead of
replacing the legacy tax_article column (which is still used by other flows).
Position it after item_tax_template; leave legacy untouched. Idempotent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-user grid layouts (GridView in __UserSettings) saved before this feature
list the legacy tax_article column but not custom_tax_articles_display. Frappe
renders the saved list verbatim and ignores the new in_list_view field, so the
'Tax Articles' cell never appears for those users until added via the grid gear
-- which is why it showed on a fresh user (Administrator) but not on machines
where the user had a saved layout.
Patch replaces the legacy tax_article entry with custom_tax_articles_display in
every saved Sales Invoice Item layout that lacks it. Idempotent; runs on migrate
so every machine converges automatically.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
create_custom_fields() applies only the properties present in each field
def (cf.update(df)). The custom_tax_articles_display def listed
in_list_view=1 but omitted 'hidden', so a site where the field had
hidden=1 would never converge on migrate -> the column showed on one
machine and stayed hidden on another. Make hidden=0 explicit so migrate
force-applies it everywhere.
Verified: forcing hidden=1 then running create_custom_fields() now resets
it to hidden=0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frappe's money_in_words() produces "AZN Dörd Min Doqquz Yüz yalnız." for
1234.00 AZN — grammatically gettext-friendly but not how Azerbaijani
documents actually look. Native convention is "Min iki yüz otuz dörd
manat 56 qəpik": sentence-case number, lowercase currency word, fraction
as digits, no "only" suffix.
format_in_words_az is hooked as a validate doc_event on every doctype
that carries in_words / base_in_words (11 in total: Sales/Purchase Order,
Sales/Purchase Invoice, Quotation, Delivery Note, etc.). Runs after the
controller's own set_total_in_words has populated the field, then
overwrites it. No-op for any language other than az.
Uses grand_total (not rounded_total) to preserve qəpik precision, since
ERPNext's Rounded Total can round to whole manat per the Currency's
rounding_method.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
migrate_standard_field_codes_to_labels() rewrote Bank Integration Excel Column
Mapping.standard_field from old machine codes to human-readable labels — a
one-time compatibility fix after the storage format changed. The migration has
done its job: the preset fixture already ships labels, and new sites create
mappings via the UI dropdown (labels from the start), so old codes can only
exist on databases that predate the format change. Drop the function, its
code->label map, and the after_migrate call.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The last line of create_custom_fields() was a print() with no code under it —
a leftover heading for setup that was never written. It only produced a
misleading line in the migrate log; VAT calculation is actually wired up by
patch_sales_documents(), an unrelated function.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Importing Bank Transactions via "Import From → Load from Excel" left
the screen darkened and unresponsive after the job finished. Root cause
was the show_progress Bootstrap modal: closing it from JS (we had a
hand-rolled _closeProgress that called .modal('hide').remove() and then
removed .modal-backdrop manually) interrupted Bootstrap's hide animation
mid-flight, leaving the dimmed overlay stuck. Opening a msgprint right
after made it worse — the two modals fought over the same backdrop
node.
Switched to frappe.dom.freeze / unfreeze: a single full-page overlay
managed by Frappe core, with no Bootstrap modal/backdrop juggling. The
freeze message is updated in place as progress events arrive (writes
to #freeze .freeze-message directly to avoid stacking the freeze
counter, which would break unfreeze pairing). unfreeze on completion
cleans up reliably, so the msgprint that opens immediately after lands
on a clean page.
Removed the now-unused _closeProgress helper.
Bank Account is chosen explicitly in the Import dialog before the
parser runs, so the IBAN→Bank Account child table never carried weight
in this flow. Unlike kapital_bank — where the API hands over raw IBANs
and needs a lookup table — jey_erp's Excel import fixes one Bank
Account per file from the dialog and applies it to every row.
Removed surface:
- Bank Statement Importer JSON: Account Mappings subtab (Mappings group)
and Accounts subtab (Data group), plus their child table field
- Bank Integration Account Mapping DocType folder
- matching.py: match_similar_accounts
- creation.py: create_unmapped_accounts
- import_api.py: "accounts" branch of get_bi_reference_data_list
- bank_statement_importer.js: Match/Create buttons in the Accounts
group, _bi_render_accounts renderer, and its load_tab line
Added post_model_sync patch drop_bank_integration_account_mapping that
deletes the DocType and its `tab<Name>` table from existing databases.
Bank Accounts created via the "Create Bank Accounts" button represent
the user's own accounts at the bank — not counterparty accounts — so
they need is_company_account=1. Without it, ERPNext treats them as
external and they don't appear in Bank Reconciliation Tool or as
selectable paid_from/paid_to in Payment Entry / Journal Entry.
Previous approach replaced the Menu Delete item's jQuery click handler,
but Frappe wires the same item through its own event chain that bypassed
.off('click') — both our cascade dialog AND the stock "Permanently
delete X?" prompt fired in sequence.
Form's Delete menu item ends up calling frm.savetrash(); overriding that
method directly is the single point that intercepts every code path that
reaches it. Removed the brittle DOM-walking wireMenuItem helper.
Stock delete on Bank Statement Importer hits Frappe's link-check
(registry rows + Bank Account dynamic-link) and refuses to proceed,
leaving users unable to remove an importer they no longer need without
hand-cleaning every dependent row.
New flow:
- cascade_delete.py adds two whitelisted endpoints: get_dependents
(counts) and cascade_delete (unlink Bank Accounts → delete Customer/
Supplier/Purpose registry rows → delete the importer)
- bank_statement_importer.js overrides the Menu → Delete action: shows
a confirm dialog listing exactly how many records will be deleted vs
just unlinked, with a red primary button to drive the choice home.
After success the user is sent to the list view.
Bank Accounts are intentionally unlinked rather than deleted — they hold
business data (Bank Transactions, reconciliations) that has nothing to
do with the importer being removed.
System is single-company; Default Company on Bank Statement Importer
duplicated what ERPNext already exposes via Global Defaults. Removed
the field and replaced bi.default_company with erpnext.get_default_company()
at the two usage sites (Bank Account creation, Bank Transaction bulk
import). Existing column on tabBank Statement Importer is left in place
— Frappe just ignores it.
The 4-step workflow stepper added in 83a2cb6 was experimental and
turned out to be visual noise on top of the form. Reverting back to
the tab layout speaking for itself.
Users had no way to know the setup is ordered — they would land on
Match/Create buttons before the registries had anything to match, or
go to Load Data before configuring the file format.
A small four-step indicator now sits at the top of the form, with the
current step highlighted and a one-line hint pointing at the next
action. State is read off the form itself (column_mappings filled? any
mapping tables populated?), so the guide moves forward as the user
makes progress and disappears once setup is done as much as the form
can tell.
The four steps:
1. Configure format → File Format tab + sample file
2. Load registries → Load Data button
3. Match & Create → Data tab + Match/Create toolbar buttons
4. Import statements → Bank Transaction list → Import From
Previously a re-upload (clear → re-attach) just refreshed the Excel
Column dropdown and silently kept the existing mapping rows — pointing
the user at the new file but leaving the table tied to the old one. Now
the form asks before overwriting: empty table still auto-detects
silently, populated table prompts with a Confirm dialog before clearing
and re-running.
The validate() hook threw "Amount Mode 'X' requires these Standard
Fields: Reference Number, Debit, Credit" as soon as the user uploaded a
sample file — auto-detect made the form dirty, and any later save
ran into a setup the user hadn't finished yet. The dialog interrupted
the very flow it was supposed to support.
Removed the entire _validate_file_format hook. The same check already
lives in excel_parser.parse_excel — it fires only when the user actually
tries to import a statement, which is the right moment to demand that
the format be complete. Saving a half-configured form is fine: users
can come back and finish later.
Auto-detect inserts a row for every header found in the sample file,
including ones it couldn't match against the synonym list — those rows
went in with an empty standard_field. With Standard Field marked
reqd=1, any subsequent action that triggered a save (Match accounts,
Create customers, etc. routed through the "save first?" prompt) failed
with "Mandatory fields required in table Column Mappings, Row N".
The reqd was already redundant: excel_parser skips column_mappings rows
without a standard_field, and Bank Statement Importer.validate enforces
the actual required-minimum set (Date / Reference + Debit/Credit or
Amount) at the parent level based on amount_mode. Dropping reqd from
the child lets unmapped rows coexist in the table for visibility.
The header → Standard Field dictionary is data, not code, so it now
lives in header_synonyms.json next to its loader. Editing a synonym list
no longer requires touching Python — open the JSON, append a string,
done.
header_synonyms.py is now a thin loader (reads the JSON once at import
time) plus the matcher and the Azərbaycan→Latin transliteration table,
which stay in Python because they're behaviour, not data.
The _HEADER_SYNONYMS dict, the Azərbaycan-to-Latin translation table, and
the matcher helpers were sitting in the middle of excel_parser.py making
the file harder to scan. Moved them to a dedicated header_synonyms.py
with a docstring explaining how to extend it.
Public API of the new module is just guess_standard_field(header) and
HEADER_SYNONYMS — that's the surface excel_parser actually uses.
Two changes that drop the manual step from the column-mapping setup:
- Auto-detect now runs implicitly when the user attaches a Sample Excel
File. The Auto-detect Columns Button field is removed (Preview Sample
stays — it answers a different question). Auto-detect only fires when
the Column Mappings table is empty; a user-edited table is never
overwritten silently.
- Replaced the modal "Auto-detect Complete" / "Sample File Error" /
"Empty Header Row" msgprints with small bottom-right show_alert
toasts. The result is one line — "Auto-mapped 9 of 11 columns" —
with green/orange/red indicator instead of a full-screen dialog.
The synonym dictionary used by detection lives in code at
jey_erp/bank_integration/excel_parser.py (_HEADER_SYNONYMS).
The two helpers used to live in the form toolbar under a 'File Format'
group, which added noise to the already busy header (Match/Create
buttons for accounts/customers/suppliers/purposes plus Load Data are
also there).
They are now native Button fields inside the File Format tab, sitting in
a two-column row directly under the Sample Excel File attach widget:
[ Sample Excel File: ... ]
[ Preview Sample ] [ Auto-detect Columns ]
[ Format / Column Mappings sections ]
Both buttons are gated by depends_on=sample_file, so they only appear
when there's actually a file to operate on — same gating as before, just
contextually placed next to the upload instead of in the global toolbar.