Compare commits

..

3248 Commits

Author SHA1 Message Date
MochaMind
9c82d192d0 fix: Swedish translations 2026-05-18 01:15:22 +05:30
MochaMind
da8b767d74 fix: Bosnian translations 2026-05-17 01:14:52 +05:30
MochaMind
9babe1dd22 fix: Croatian translations 2026-05-17 01:14:48 +05:30
MochaMind
010d77e8ea fix: Persian translations 2026-05-17 01:14:44 +05:30
MochaMind
0a054500c9 fix: Croatian translations 2026-05-15 01:13:12 +05:30
MochaMind
b11c51a7e5 fix: Swedish translations 2026-05-15 01:13:03 +05:30
MochaMind
4380d710c7 fix: sync translations from crowdin (#54893)
* fix: Persian translations

* fix: Croatian translations

* fix: Swedish translations
2026-05-14 13:30:43 +02:00
Mihir Kandoi
78a79120ea fix: status not changing for dropshipped POs and SOs (#54934)
* fix: status not changing for dropshipped POs and SOs

* test: change test case to accomodate new flow
2026-05-14 08:26:51 +00:00
Khushi Rawat
930990434c Merge pull request #54935 from frappe/revert-54176-payment-entry-list-reconciliation-indicator
Revert "feat: show reconciled/unreconciled indicator in list view"
2026-05-14 12:52:35 +05:30
Khushi Rawat
c5e24eda69 Revert "feat: show reconciled/unreconciled indicator in list view" 2026-05-14 12:35:57 +05:30
rohitwaghchaure
06784d2a46 Merge pull request #54905 from rohitwaghchaure/fixed-support-67449
fix: posting date and time
2026-05-13 23:47:38 +05:30
Pandiyan P
f9dec73042 fix(stock): add whole number quantity validation in Stock Reconciliation (#54922) 2026-05-13 20:11:34 +05:30
rohitwaghchaure
3c993377aa chore: fix linter issue 2026-05-13 18:50:09 +05:30
Nishka Gosalia
45f05fbeaa fix(UX): Buying settings form cleanup (#54731)
* fix(UX): Buying settings form cleanup

* fix: controller approach modification

* fix: dark mode support
2026-05-13 14:53:04 +05:30
Mihir Kandoi
cf5e8ce878 Revert "fix: debit credit not equal in purchase transactions for mult… (#54906)
* Revert "fix: debit credit not equal in purchase transactions for multi currency"

This reverts commit 75bcea57f4.

* Revert "test: add test case"

This reverts commit 1d30a202c3.

* Revert "fix: include rejected qty in tax (purchase receipt)"

This reverts commit 8c9a88abbe.
2026-05-13 08:57:52 +00:00
rohitwaghchaure
c740f77a6f chore: fixed test case 2026-05-13 14:05:55 +05:30
Rohit Waghchaure
fb6c05f186 fix: posting date and time 2026-05-13 13:16:00 +05:30
Pandiyan P
bc07b2d3e5 fix: add warehouse vaildation for repack entry (#54866) 2026-05-13 11:51:49 +05:30
Loïc Oberle
d80a52ae22 refactor(supplier): Using query builder for get_rfq_total_items (#54877)
Use the query builder for get_rfq_total_items to assure compatibility with PostgreSQL.
2026-05-12 17:22:23 +00:00
Loïc Oberle
d128fb92cf refactor(supplier): Using query builder for get_total_accepted_amount (#54873)
Use the query builder for get_total_accepted_amount to assure compatibility with PostgreSQL.
2026-05-12 17:14:30 +00:00
Loïc Oberle
66914ac2fc refactor(supplier): Using query builder for get_total_rejected_items (#54872)
Use the query builder for get_total_rejected_items to assure compatibility with PostgreSQL.
2026-05-12 22:37:04 +05:30
Loïc Oberle
20d6b54590 refactor(supplier): Using query builder for get_total_received_items (#54870)
Use the query builder for get_total_received_items to assure compatibility with PostgreSQL
2026-05-12 22:36:07 +05:30
Loïc Oberle
573e37a78d refactor(supplier): Using query builder for get_total_rejected_amount (#54871)
Use the query builder for get_total_rejected_amount to assure compatibility with PostgreSQL.
2026-05-12 22:35:47 +05:30
Loïc Oberle
7a292f9ea6 refactor(supplier): Using query builder for get_total received (#54868)
Use the query builder for get_total_received to assure compatibility with PostgreSQL.
2026-05-12 22:34:30 +05:30
Loïc Oberle
876d4bdb75 refactor(supplier): Using query builder for get_sq_total_number (#54878)
Use the query builder for get_sq_total_number to assure compatibility with PostgreSQL.
2026-05-12 22:33:24 +05:30
Loïc Oberle
24530fa349 refactor(supplier): Using query builder for get_total_shipments (#54875)
Use the query builder for get_total_shipments to assure compatibility with PostgreSQL.
2026-05-12 22:32:57 +05:30
Loïc Oberle
5b7f07ddb1 refactor(supplier): Using query builder for get_total_received_amount (#54869)
Use the query builder for get_total_received_amount to assure compatibility with PostgreSQL.
2026-05-12 22:32:34 +05:30
Loïc Oberle
1a4748759d refactor(supplier): Using query builder for get_total_accepted_items (#54874)
Use the query builder for get_total_accepted_items to assure compatibility with PostgreSQL.
2026-05-12 22:31:34 +05:30
Loïc Oberle
c8f91ac4db refactor(supplier): Using query builder for get_rfq_total_number (#54876)
Use the query builder for get_rfq_total_number to assure compatibility with PostgreSQL.
2026-05-12 22:30:49 +05:30
Loïc Oberle
1e7a265037 refactor(supplier): Using query builder for get_sq_total_items (#54879)
Use the query builder for get_sq_total_items to assure compatibility with PostgreSQL.
2026-05-12 22:30:21 +05:30
Loïc Oberle
1b9eaed4d2 refactor(supplier): Using query builder for get_rfq_response_days (#54880)
Use the query builder for get_rfq_response_days to assure compatibility with PostgreSQL.
2026-05-12 22:29:31 +05:30
Soham-ambibuzz
5560f6c270 feat: Added Philippines chart of account json file (#53918)
* feat: Added philipinnes chart of account json file

Signed-off-by: Soham-ambibuzz <soham.pawar@ambibuzz.com>

* feat: made changes as per review comments and corrected indentation

* feat: made changes as per review comments

* feat: made changes as per review comments to resolve the issues

* fix: fixed changes as per review comments

Signed-off-by: soham7117 <sohampawar626@gmail.com>

* fix: fixed changes as per review comments on bank group account

Signed-off-by: soham7117 <sohampawar626@gmail.com>

---------

Signed-off-by: Soham-ambibuzz <soham.pawar@ambibuzz.com>
Signed-off-by: soham7117 <sohampawar626@gmail.com>
Co-authored-by: soham7117 <sohampawar626@gmail.com>
2026-05-12 21:48:55 +05:30
diptanilsaha
9134db9cd3 fix: added permission validation for deactivate_sales_person (#54884) 2026-05-12 16:01:08 +00:00
MochaMind
6e349569c7 fix: sync translations from crowdin (#54810)
* fix: Swedish translations

* fix: Croatian translations

* fix: Bosnian translations

* fix: French translations

* fix: Arabic translations

* fix: Czech translations

* fix: Danish translations

* fix: German translations

* fix: Hungarian translations

* fix: Italian translations

* fix: Dutch translations

* fix: Polish translations

* fix: Portuguese translations

* fix: Russian translations

* fix: Slovenian translations

* fix: Serbian (Cyrillic) translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Vietnamese translations

* fix: Portuguese, Brazilian translations

* fix: Indonesian translations

* fix: Persian translations

* fix: Thai translations

* fix: Croatian translations

* fix: Burmese translations

* fix: Bosnian translations

* fix: Norwegian Bokmal translations

* fix: Serbian (Latin) translations

* fix: Spanish translations

* fix: Esperanto translations

* fix: Swedish translations

* fix: Croatian translations

* fix: Bosnian translations
2026-05-12 21:10:00 +05:30
Jaypal Lakum
3532c1cc69 fix(task): update depends_on for closing date and review date #54850 (#54852) 2026-05-12 09:53:42 +00:00
Mihir Kandoi
b5527cf328 fix: raw material should not have target warehouse in manufacture entry (#54849) 2026-05-12 14:56:59 +05:30
Nishka Gosalia
631958314f Merge pull request #54835 from nishkagosalia/st-67801
fix: rename supplier wise stock analytics report
2026-05-12 12:37:36 +05:30
Nikhil Kothari
422ff15be5 fix: remove wrapper for list items in error messages (#54848) 2026-05-12 05:52:39 +00:00
Loïc Oberle
1d5ef62452 refactor(supplier): use frappe orm for criteria retrieval (#54841)
replace raw SQL with frappe.get_all in get_criteria_list to leverage
the standard Frappe API. This improves code readability and follows
framework best practices.
2026-05-11 21:27:10 +05:30
Loïc Oberle
2e958de95b refactor(supplier): use frappe orm for database queries (#54842)
replace raw SQL with frappe orm to leverage the framework's native
capabilities. this improves code maintainability and adheres to frappe
best practices.
2026-05-11 21:24:28 +05:30
Loïc Oberle
0729c9a9cd refactor(material-request): replace raw SQL with Frappe Query Builder (#54836)
* refactor(material-request): replace raw SQL with Frappe Query Builder

Replace frappe.db.sql with frappe.qb in get_linked_material_requests
to improve readability and leverage the ORM's built-in SQL injection protection.

* removes unused import
2026-05-11 12:10:04 +00:00
Mihir Kandoi
95705f18aa fix: validate variant values (#54831) 2026-05-11 12:00:57 +00:00
nishkagosalia
85206e0278 fix: rename supplier wise stock analytics report 2026-05-11 16:24:57 +05:30
ruthra kumar
0f9cfeb2ef Merge pull request #54828 from ruthra-kumar/faster_payment_reconciliation_tests
refactor(test): speed up payment reconciliation tests
2026-05-11 14:09:45 +05:30
Jatin3128
dfbe847307 fix(general-ledger): show raw GL entries when categorize_by is empty (#54816) 2026-05-11 13:41:23 +05:30
ruthra kumar
f58242dca7 refactor(test): speed up payment reconciliation tests 2026-05-11 13:21:01 +05:30
Mihir Kandoi
23e9ad3fd9 fix: check if item is dropshipped before updating quantity (#54825) 2026-05-11 07:46:27 +00:00
Nikhil Kothari
f4008adc16 fix: UI/UX issues in new banking module (#54824)
* fix: enforce user permissions on bank account get_list

* feat: auto-select last used bank account

* fix: skeleton loaders in bank balance

* fix: show empty state for no bank transactions

* chore: add Stripe and PayPal logos

* fix: alignment of header text in list-view

* fix: wrap words in transaction description

* fix: change file-dropzone color on hover
2026-05-11 07:32:11 +00:00
Mihir Kandoi
03acbc3dc9 fix: do not rely on client side to update quantities during partial d… (#54804)
fix: do not rely on client side to update quantities during partial dropship
2026-05-11 06:17:54 +00:00
ruthra kumar
3deda25d21 Merge pull request #54783 from ruthra-kumar/prevent_editing_reversal_journals
fix: disallow editing on reversal journals
2026-05-11 10:08:18 +05:30
MochaMind
8c3739eb08 chore: update POT file (#54815) 2026-05-10 13:56:37 +02:00
Nikhil Kothari
346f080538 chore: update frappe-react-sdk (#54811) 2026-05-09 18:58:15 +00:00
dependabot[bot]
09d772f92e chore(deps): bump socket.io-parser from 4.2.5 to 4.2.6 in /banking (#54807)
Bumps [socket.io-parser](https://github.com/socketio/socket.io) from 4.2.5 to 4.2.6.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.5...socket.io-parser@4.2.6)

---
updated-dependencies:
- dependency-name: socket.io-parser
  dependency-version: 4.2.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 18:35:33 +00:00
dependabot[bot]
4dfe532475 chore(deps): bump axios from 1.13.5 to 1.16.0 in /banking (#54806)
Bumps [axios](https://github.com/axios/axios) from 1.13.5 to 1.16.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.13.5...v1.16.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.16.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 18:12:04 +00:00
Nikhil Kothari
6de5367f12 feat: new banking module (#54720)
* feat: initial SPA setup for banking

* wip: bring over new banking module

* feat: added Espresso design tokens

* feat: button styles

* fix: add all ink colors

* wip: espresso design system changes

* feat: button and badge espresso components

* fix: button styling for reconcile

* feat: Espresso progress bar

* feat: Espresso toggle switch

* feat: Espresso tabs design

* fix: vertical tab support

* fix: button sizing across modals

* feat: Espresso style table layout

* feat: Espresso tooltip

* feat: Espresso elevations and checkbox

* feat: Dialog with Espresso styles

* feat: Espresso textarea

* fix: input styles

* fix: colors on bank picker

* fix: breadcrumb styling

* fix: bank picker styling

* feat: create doctypes and fields for bank reconciliation

* feat: APIs for banking

* fix: use date format parser

* fix: font styling to match Espresso

* wip: settings modal

* feat: settings dialog component

* fix: icons and invalid requests

* feat: preferences tab

* fix: adjust icon stroke width to 1.5

* feat: rule configuration in settings

* fix: remove sheet component

* feat: alert and error banner component

* feat: dropdown in Espresso

* feat: popover and select in Espresso

* fix: cleanup more styles

* fix: match size of link fields

* feat: command styling

* fix: remove unused style tokens

* fix: styles for global date picker dropdown

* fix: styles for match and reconcile

* feat: table Espresso component

* feat: remove all other design tokens

* fix: remove unused tokens

* fix: form elements

* fix: remove unused styles and fix filters in bank transaction list

* feat: fetch bank rec doctypes for filtering

* fix: record payment modal

* feat: support for dark mode switching

* fix: move bank logos to public folder

* feat: add support for RTL

* feat: support for RTL

* chore: send layout direction in dev boot

* fix: make checkbox work in RTL

* feat: dark mode support

* fix: dark mode style

* feat: bank logos in dark mode

* feat: dark mode bank logos

* chore: use dark mode bank logos everywhere

* chore: move rule evaluation to controller

* chore: add tests for bank transaction rules

* fix: move deps to fix actions errors

* fix: move tw-animate-css to deps

* fix: remove shadcn

* fix: do not open modal if no transactions selected

* fix: add translation strings

* feat: add banner on existing bank reconciliation tool

* feat: bank statement import

* fix: translations and layout directions

* fix: validation for transaction matching rule

* fix: styles

* fix: show conflicting transactions in alert

* fix: show help text for new banking module forms

* feat: show total debits and credits

* fix: dark mode colors in automatic config

* feat: add keyboard shortcuts help

* feat: added keyboard shortcut for settings

* fix: decrease size of progress bar

* chore: bump packages

* feat: add tests for statement import

* fix: settings dialog

* fix: show banner on small screens

* fix: show banner when no bank account set
2026-05-09 23:14:58 +05:30
MochaMind
332026fe5e fix: sync translations from crowdin (#54683) 2026-05-08 22:34:27 +02:00
Raffael Meyer
992800f3dd fix: implement get_notification_email hook on Opportunity, Prospect and Customer (#54789) 2026-05-08 22:32:38 +02:00
Mihir Kandoi
db74360396 feat: partial delivery in dropshipping (#54787) 2026-05-08 15:30:53 +00:00
Pandiyan P
0b6a372a52 fix(stock): ignore reserved qty for stock levels in batch (#54790) 2026-05-08 17:51:59 +05:30
Sakthivel Murugan S
a4a389bd41 fix(crm): handle empty _assign in appointment auto assignment (#54782) 2026-05-08 17:51:08 +05:30
Sudharsanan Ashok
4e850f31d5 fix(stock): priorities pick list parent warehouse (#54788) 2026-05-08 17:50:00 +05:30
Raffael Meyer
6f9f3d0a5c feat(Lead)!: send notifications to lead owner (#53959) 2026-05-08 12:40:23 +02:00
ruthra kumar
26ca7445eb fix: disallow editing on reversal journals 2026-05-08 12:27:32 +05:30
Khushi Rawat
ddc6d2c4e0 Merge pull request #53934 from Shllokkk/financial-statements-print-formats
feat: Financial Statements print format introduction
2026-05-08 12:05:24 +05:30
ruthra kumar
385835a167 Merge pull request #51723 from nlvegan/feat/payment-controller-v2-support
feat(payments): Add PaymentController v2 gateway support
2026-05-08 10:26:45 +05:30
Loïc Oberle
548e9a26db refactor(purchase-order): use ORM syntax for min order quantity query (#54778)
* refactor(purchase-order): use ORM syntax for min order quantity query

Use frappe.get_all instead of raw SQL with manual string formatting
to fetch min_order_qty. This improves code readability and leverages
the framework's built-in database abstraction.

* chore: fix formatting

* chore: fix formatting

* chore: fix formatting by adding a space
2026-05-07 20:35:45 +05:30
Loïc Oberle
d04aa4408d fix(stock): use case instead of if in get_reserved_qty for postgres (#54763)
Fixes get_reserved_qty on stock balance to use case instead of if to support postgresql
2026-05-07 11:02:28 +00:00
Loïc Oberle
bbb6d7c004 refactor(buying): replace raw sql with orm in supplier scorecard (#54771)
Use frappe.get_all instead of frappe.db.sql to fetch standings list.
2026-05-07 10:55:06 +00:00
Pandiyan P
0fc96e8f7d fix(stock): apply filters for rejected warehouse in pick list (#54733) 2026-05-07 15:58:57 +05:30
ruthra kumar
d4bf9ee0ec Merge pull request #54461 from Jatin3128/CL_pre_submit
feat: add pre-submit credit limit warning on save
2026-05-07 10:04:30 +05:30
Shllokkk
e82b4d9ca7 fix: add filter subtitle in print formats 2026-05-06 17:45:33 +05:30
Mihir Kandoi
d5549e2f6c feat: stock reservation for product bundle (#54750)
* feat: stock reservation for product bundle

* test: add test case
2026-05-06 16:39:04 +05:30
Shllokkk
5858b14071 fix: styling in trial_balance.html and print format 2026-05-06 16:17:03 +05:30
Shllokkk
e8777a1e34 refactor: print templates for financial statements 2026-05-06 16:17:03 +05:30
Shllokkk
fa0a9085ca fix: minor text issues in print 2026-05-06 16:17:03 +05:30
Shllokkk
ac7e5271b0 feat: print format for report trial balance 2026-05-06 16:17:03 +05:30
Shllokkk
82cac9c40f feat: introduce print formats for financial statements 2026-05-06 16:17:03 +05:30
rohitwaghchaure
75804a364b Merge pull request #54757 from rohitwaghchaure/fixed-support-67550
fix: incorrect serial nos picked during disassemble
2026-05-06 15:06:45 +05:30
Rohit Waghchaure
25f7fa548d fix: incorrect serial nos picked during disassemble 2026-05-06 14:24:43 +05:30
Mihir Kandoi
28d9c2ca68 Revert "ci: auto merge backports" (#54754)
* Revert "ci: auto merge backports"

This reverts commit dfe1a5749a.

* revert: propogate label
2026-05-06 06:04:34 +00:00
Farouk Guerdelli
8efdab7e96 Revise CONTRIBUTING.md for clarity and formatting (#54739)
Updated the contributing guidelines for clarity and consistency. Improved language and formatting for better readability.
2026-05-06 05:33:26 +00:00
Mihir Kandoi
907a809f3f fix: incorrect validation thrown for drop shipped PI (#54751) 2026-05-06 05:30:14 +00:00
MochaMind
7028034cd6 chore: update POT file (#54709) 2026-05-05 21:28:05 +05:30
rohitwaghchaure
757923b482 Merge pull request #54723 from rohitwaghchaure/fixed-support-59821
fix: decimal issue in stock ageing report
2026-05-05 16:41:56 +05:30
Nishka Gosalia
2370d04b41 Merge pull request #54732 from nishkagosalia/st-67351 2026-05-05 16:20:50 +05:30
Sakthivel Murugan S
fb7f9a81d4 fix: hide payment and payment request buttons based on permissions in invoices and orders (#53920)
Co-authored-by: ravibharathi656 <ravibharathi656@gmail.com>
2026-05-05 11:46:12 +05:30
nishkagosalia
f86568b078 fix: Remove bom stock report link from manufacturing workspace 2026-05-05 11:18:05 +05:30
foppe
b9e40a42b8 test(payments): add tests for v2 gateway detection, tx_data, and contact/address handling 2026-05-04 21:49:49 +02:00
foppe
4f8cc1359b feat(payments): add PaymentController v2 gateway support
Add support for the new PaymentController interface from frappe/payments,
enabling Payment Request to work with v2 gateways while maintaining
backward compatibility with v1.

Related: frappe/payments#192
2026-05-04 21:49:48 +02:00
Mihir Kandoi
0cd0b8213d ci: Upgrade github-script action to version 8 (#54726) 2026-05-04 16:08:56 +00:00
Mihir Kandoi
2d3190effb fix: error when creating quotation from CRM (#54722) 2026-05-04 15:41:09 +00:00
Rohit Waghchaure
542eb6aca4 fix: decimal issue 2026-05-04 20:59:38 +05:30
Jatin3128
55619be732 feat: pre-submit validation error for packed quantity mismatch 2026-05-04 16:31:06 +05:30
Mihir Kandoi
a68769565b refactor: remove old subcontracting flow (#54717) 2026-05-04 14:06:59 +05:30
mergify[bot]
19234cafbe fix: accounts and account types in German CoA "SKR 03" (backport #54711) (#54712)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2026-05-03 17:49:03 +00:00
Mihir Kandoi
09623d4c0c refactor: update_child_qty_rate function (#54706) 2026-05-02 23:58:58 +05:30
Mihir Kandoi
032a282f84 ci: auto merge backports (#54701)
* ci: auto merge backports

* ci: add github action to propogate auto-merge label
2026-05-02 17:11:39 +00:00
mergify[bot]
ca093177e0 fix: set valid_from in created Item Price (backport #54696) (#54699)
* fix: set valid_from in created Item Price (#54696)

Co-authored-by: Kaajal-Chhattani <kaajal.chhattani@aurigait.com>
(cherry picked from commit 6246a9aa6e)

# Conflicts:
#	erpnext/stock/get_item_details.py

* chore: resolve conflicts

---------

Co-authored-by: Kaajalchhattani <89331214+Kaajalchhattani@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-05-02 16:29:16 +00:00
Kenneth Sequeira
ea3cf57042 fix: update frappe docker badge and link (#54702)
* fix: update frappe docker badge and link

* remove pwd link
2026-05-02 21:55:29 +05:30
rohitwaghchaure
06ffe52d6e Merge pull request #54681 from rohitwaghchaure/fixed-support-66529
fix: incorrect expense account book in purchase return
2026-05-01 08:12:31 +05:30
Raffael Meyer
c120cc7ed1 fix: add missing fields in set_currency_labels (#54689) 2026-05-01 03:54:14 +02:00
Raffael Meyer
25be38e23c fix: Backfill not_applicable on Item Tax Template Details for German companies (#54682) 2026-04-30 19:21:24 +00:00
Rohit Waghchaure
2a720e7008 fix: incorrect expense account book in purchase return 2026-04-30 20:36:20 +05:30
Raffael Meyer
f38eca9124 fix: mark item tax templates as not applicable (#54673)
* fix: mark item tax templates as not applicable

For new German charts of accounts, mark accounts for different tax rates as *Not Applicable* in **Item Tax Templates**.

* fix: wrong applicable rate 19 in template 7
2026-04-30 11:44:08 +00:00
rohitwaghchaure
ad89f88c93 Merge pull request #54664 from rohitwaghchaure/fixed-support-66924
fix: show in and out qty in the stock ledger report for stock recos
2026-04-30 14:13:42 +05:30
Trusted Computer
78f654765d fix: correct titles set to {customer_name} or {supplier_name} text strings (#54656) 2026-04-30 10:28:14 +02:00
Hemil-Sangani
231dd1856f fix(project): use user.email for invitations and skip disabled users. (#54561)
* fix(project): use user.email for invitations and skip disabled users.

* Update erpnext/projects/doctype/project/project.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(project): remove duplicate loop causing indentation error

* fix(project): resolve pre-commit hook failure

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-30 07:53:32 +00:00
Rohit Waghchaure
da081254a6 fix: show in and out qty in the stock ledger report for stock recos 2026-04-30 13:16:13 +05:30
Raffael Meyer
c543d15f3c feat: copy terms attachments to transactions (#53403) 2026-04-29 21:14:58 +00:00
Khushi Rawat
ddf0e35009 Merge pull request #54658 from khushi8112/skip-rescheduling-for-fully-depreciated-asset-sale
fix: skip depreciation rescheduling when asset is fully depreciated on sale
2026-04-30 02:34:10 +05:30
khushi8112
88b82383f5 fix: skip rescheduling only for asset being disposed 2026-04-30 02:11:57 +05:30
khushi8112
c4155b6c81 fix: skip depreciation rescheduling when asset is fully depreciated on sale 2026-04-30 02:01:57 +05:30
Mihir Kandoi
a04c028522 fix: correct project filter in buying doctypes (#54644) 2026-04-29 11:27:47 +00:00
diptanilsaha
5c5a5361bc fix(payment_entry): convert the date args to string type before escaping in get_outstanding_reference_documents (#54639) 2026-04-29 16:38:57 +05:30
Mihir Kandoi
060defcc2b fix: dont show serial/batch button when PR is submitted (#54642) 2026-04-29 16:36:31 +05:30
Mihir Kandoi
d0d8cff48f fix: py error on sales forecast doctype (#54641)
fix: py error on sales forecase doctype
2026-04-29 10:49:05 +00:00
Nishka Gosalia
844f3dbc0b feat(ux): Naming series dialog (#54554) 2026-04-29 14:45:10 +05:30
Khushi Rawat
43937acd8b fix(UX): Item master form cleanup (#54538)
* fix: UI improvements for item form

* fix: add descriptions and tooltips to all checkboxes

* feat: show toast notification when item price is created

* fix: do not use selling rate for opening stock entry

* fix: add descriptions and tooltips to item default fields

* fix(test): give valuation rate for opening stock entry creation

* fix: moving naming series toggle before the return

* refactor: more changes in the form UI
2026-04-29 14:44:55 +05:30
Mihir Kandoi
503b5bf140 perf: max recursion depth error in serial no (#54629) 2026-04-29 08:34:08 +00:00
rohitwaghchaure
3542087003 Merge pull request #54567 from barredterra/sn-ledger-status
fix: show correct status in Serial No Ledger
2026-04-29 12:42:54 +05:30
Pandiyan P
d68801e73a fix(selling): blanket order ordered qty recalculation on sales order status change (#54593) 2026-04-29 11:57:40 +05:30
Nishka Gosalia
addec3aa8f Merge pull request #53295 from aerele/project-not-copied-from-first-item-row 2026-04-29 11:38:07 +05:30
MochaMind
b001884f9d fix: sync translations from crowdin (#54607) 2026-04-29 01:52:16 +05:30
Ravibharathi
d1a80d40c4 fix: avoid double reduction of pe reference outstanding (#54193)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-29 01:46:55 +05:30
Ravibharathi
a8030c9713 fix: filter overdue purchase order items by company (#54099) 2026-04-29 00:58:01 +05:30
Mihir Kandoi
54f20de7e3 fix: duplicate entries being shown in batch exists in future transact… (#54604)
fix: duplicate entries being shown in batch exists in future transactions msg
2026-04-28 16:28:53 +00:00
diptanilsaha
f8893b04d5 refactor(sms_center): replaced raw SQL queries with Query Builder (#54600) 2026-04-28 15:15:46 +00:00
Lakshit Jain
1bade56e37 Merge pull request #54362 from frappe/ignore-opening-check
fix: filter opening entries after closing voucher
2026-04-28 18:43:25 +05:30
Lakshit Jain
a2b96799ff Merge pull request #54517 from vorasmit/exclude-pcv
fix: always exclude pcv entries except for closing account head
2026-04-28 18:42:42 +05:30
Smit Vora
d0f0e38e8d Merge pull request #54479 from Abdeali099/cash-flow-fixes 2026-04-28 17:30:32 +05:30
Smit Vora
590f2ffe28 test: include both accounts to test sum = 0 2026-04-28 16:45:26 +05:30
diptanilsaha
084c7f72f0 fix(get_stock_balance): validate inventory dimension fieldnames (#54587) 2026-04-28 16:41:22 +05:30
Smit Vora
84aa54c540 test: pcv is excluded from PL accounts 2026-04-28 16:30:02 +05:30
Smit Vora
5fc3ca1d4b test: opening entries after period closing 2026-04-28 16:02:32 +05:30
diptanilsaha
d62fa3c464 fix(payment_entry): escape arguments on invoice and order fetching sql queries (#54582) 2026-04-28 15:55:45 +05:30
diptanilsaha
07337ba9da chore(sidebar): moved Inactive Customers from CRM to Selling Workspace Sidbar (#54578) 2026-04-28 09:31:51 +00:00
Mihir Kandoi
2088a01c19 fix: update status of quotation in patch (#54577) 2026-04-28 09:20:41 +00:00
ravibharathi656
68cc518497 fix: copy project to new item row from parent 2026-04-28 13:10:48 +05:30
Sudharsanan Ashok
6f9089dd5b fix(manufacturing): remove conversion factor for stock qty (#54525) 2026-04-28 10:45:54 +05:30
Vinay Mishra
63edd5ddc6 fix: negative quantity check in validate_item_qty (#54559)
Fix negative quantity check in validate_item_qty

When saving a Blanket Order with a blank qty field in the items table, the following error is raised:

TypeError: '<' not supported between instances of 'NoneType' and 'int'

Root cause: The validate_item_qty method compares d.qty < 0 directly. When the qty field is left empty, its value is None, and Python cannot compare None with an integer.

Fix
Wrap d.qty with flt(), which safely converts None (and any non-numeric value) to 0.0 before the comparison.

# Before
if d.qty < 0:

# After
if flt(d.qty) < 0:
2026-04-28 05:12:49 +00:00
barredterra
2b3e047143 fix: show correct status in Serial No Ledger 2026-04-27 21:41:38 +02:00
barredterra
cb2e6e1e2e refactor: extract SN status logic 2026-04-27 21:41:12 +02:00
MochaMind
37e3493ec4 fix: sync translations from crowdin (#54520) 2026-04-27 20:53:04 +02:00
Mihir Kandoi
601581d6f8 fix: debit credit not equal in purchase transactions for multi currency (#54456) 2026-04-27 20:30:41 +05:30
ruthra kumar
837cdc9cc3 Merge pull request #54509 from ruthra-kumar/hide_toggleable_fields
fix: hide feature flag controlled fields on install
2026-04-27 14:43:11 +05:30
Mihir Kandoi
5281d60f2d fix: correct display depends on condition (#54548) 2026-04-27 09:07:36 +00:00
Mihir Kandoi
0aadd1e3a5 fix: make inv dimen reqd only in delivery note (#54546) 2026-04-27 08:28:55 +00:00
Pandiyan P
60a6b38c31 fix(stock): remove validation for transfer_qty field (#54542) 2026-04-27 06:56:30 +00:00
Mihir Kandoi
be2a4b7b2a refactor: quality inspection item query (#54511) 2026-04-27 10:45:25 +05:30
MochaMind
5c839f60e4 chore: update POT file (#54536) 2026-04-26 18:55:27 +02:00
rohitwaghchaure
6e77a45c05 Merge pull request #54514 from aerele/fix/incoming-rate-issue
fix(stock): set incoming rate as zero for outward sle
2026-04-26 10:06:35 +05:30
rohitwaghchaure
2a6ddc7f67 Merge pull request #54530 from aerele/fix/support-#66029
fix(stock): show item code in serial and batch selector dialog
2026-04-26 10:04:41 +05:30
Sudharsanan11
fee5bcadb2 fix(stock): add stock entry in batch master connection 2026-04-26 00:05:19 +05:30
Sudharsanan11
f572bc51e1 fix(stock): show item code in serial and batch selector dialog 2026-04-26 00:05:19 +05:30
Nishka Gosalia
fba33b7e7a refactor(UX): selling settings form (#54412)
refactor(UX): Selling settings form cleanup
2026-04-25 15:27:32 +05:30
diptanilsaha
ebca389136 fix(PCV): set correct filters of from_date and to_date on General Ledger Report on clicking Ledger button (#54522) 2026-04-25 00:03:38 +05:30
Smit Vora
c94b8c41f3 chore: comment 2026-04-24 19:32:00 +05:30
mahsem
e517eeaaa2 feat: danish_bosnian_address_template (#54093) 2026-04-24 14:54:37 +02:00
Khushi Rawat
c3931d4e29 Merge pull request #53843 from Shllokkk/ap-print-format
feat: Accounts Payable print template revamp and print format introduction
2026-04-24 17:51:45 +05:30
Khushi Rawat
0b9fdcd8cd Merge pull request #53870 from Shllokkk/arap-summary-print-formats
feat: AR and AP summary reports print template revamp and print format introduction
2026-04-24 17:42:52 +05:30
Khushi Rawat
b4e941835b Merge pull request #53822 from Shllokkk/ar-print-format
feat: Accounts Receivable print template revamp and print format introduction
2026-04-24 17:41:13 +05:30
Khushi Rawat
9132f0fc4a Merge pull request #53762 from Shllokkk/gl-print-format
feat: General ledger print template revamp and print format introduction
2026-04-24 17:39:45 +05:30
Sudharsanan11
ce37530e70 fix(stock): set incoming rate as zero for outward sle 2026-04-24 17:29:13 +05:30
ruthra kumar
889fdf2f11 fix: hide feature flag controlled fields on install 2026-04-24 17:13:36 +05:30
Smit Vora
5518e8c99f Merge pull request #54480 from ljain112/fix-change-customer 2026-04-24 13:19:32 +05:30
Smit Vora
419b9b3279 Merge pull request #54476 from ljain112/fix-tds-threshhold
fix: ensure tax withholding entries respect date range of category
2026-04-24 13:18:25 +05:30
Khushi Rawat
a9e6f8efd8 Merge pull request #53314 from aerele/budget-validation-on-cancel
fix: skip budget validation when cancelling GL entries
2026-04-24 12:14:09 +05:30
Jatin3128
26d3a25d18 feat: add pre-submit credit limit warning on save 2026-04-24 05:05:43 +05:30
Mihir Kandoi
0e20e35842 fix: preserve inventory dimensions when raw materials are reset (#54440)
* fix: preserve inventory dimensions when raw materials are reset

* test: add test case
2026-04-23 17:16:12 +00:00
Raffael Meyer
b4107b8fd5 test(Code List): check content, not filename (#54490) 2026-04-23 15:40:22 +00:00
Raffael Meyer
a165b240a7 fix(edi): hardcode "Code List" DocType in importer (#54488) 2026-04-23 13:48:18 +00:00
Abdeali Chharchhodawala
f6639db0e9 feat: enhance account category with root type (#53190) 2026-04-23 17:34:37 +05:30
Abdeali Chharchhodawala
c35221852a feat: Add XLSX styling support to custom financial report templates (#52612) 2026-04-23 17:15:41 +05:30
Abdeali Chharchhoda
3854d2cbf6 chore: minor fix 2026-04-23 17:13:01 +05:30
Sudharsanan Ashok
ab19b16fe2 fix(stock): show available qty in warehouse link field (#54474) 2026-04-23 17:08:54 +05:30
Abdeali Chharchhoda
1fd6c3ba1a fix: update account identification to avoid using name_field in financial statements 2026-04-23 17:05:33 +05:30
Abdeali Chharchhoda
4274c2aba3 fix: add filter labels and required filters for financial report validation 2026-04-23 16:29:38 +05:30
Abdeali Chharchhoda
79d6a51e1e fix: update fiscal year filter to use mandatory_depends_on instead of reqd 2026-04-23 15:54:42 +05:30
ljain112
4eb9107e22 fix: update type hint for get_item_tax_template function 2026-04-23 15:50:03 +05:30
Abdeali Chharchhoda
5a915cb45e fix: ensure fiscal year is checked before validating date filters in financial statements 2026-04-23 15:43:20 +05:30
Smit Vora
b8c3765b85 Merge pull request #54449 from vorasmit/tds-reports-refactor
refactor: tax witholding report
2026-04-23 14:57:31 +05:30
ljain112
9ead8d4e3f fix: ensure tax withholding entries respect date range of category 2026-04-23 13:35:46 +05:30
Raffael Meyer
7f8fa7cf5e ci: test correctness pattern (#54186) 2026-04-22 22:00:42 +02:00
rohitwaghchaure
fd4cedf5e4 Merge pull request #54471 from rohitwaghchaure/fixed-delivery-schedule
fix: delivery schedule in the sales order
2026-04-22 22:02:15 +05:30
Rohit Waghchaure
435db260ee fix: delivery schedule in the sales order 2026-04-22 21:36:51 +05:30
Mihir Kandoi
f5357c233d fix: py error on stock ageing report (#54467) 2026-04-22 19:45:12 +05:30
diptanilsaha
0d2da6d86c ci: fix timezone for python mariadb tests (#54464) 2026-04-22 17:46:28 +05:30
Smit Vora
0349e7a0b8 fix: always exclude pcv entries except for closing account head 2026-04-22 16:09:42 +05:30
Smit Vora
7ae91cac01 fix: summing of values could be zero even if values exist 2026-04-22 13:27:36 +05:30
Smit Vora
b925469c4d fix: add party type for dynamic link support 2026-04-22 12:06:38 +05:30
Smit Vora
f0ea20e579 refactor: make report extensible by regional apps 2026-04-22 12:04:35 +05:30
ruthra kumar
3faeb1609b Merge pull request #54447 from ruthra-kumar/test_remove_raw_sql_delete_on_setup
refactor(test): remove explicit sql delete calls
2026-04-22 11:12:59 +05:30
ruthra kumar
b16dd3f2dd refactor(test): remove explicit sql delete calls 2026-04-22 10:33:25 +05:30
MochaMind
ffae7e42d3 fix: sync translations from crowdin (#54454) 2026-04-22 00:26:57 +05:30
Smit Vora
b5550f747e test: None is better than zero, as no values exist 2026-04-21 19:33:29 +05:30
Shllokkk
f6adef45bf Merge pull request #54307 from aerele/fix/populate_project_from_pe
fix(accounts): fetch project name from payment entry to journal entry
2026-04-21 18:57:45 +05:30
Smit Vora
07b023a934 refactor: updated key for withholding_date 2026-04-21 18:45:26 +05:30
Smit Vora
53666974a3 refactor: better label for entity type 2026-04-21 18:29:50 +05:30
Smit Vora
c3e7f7f02f refactor: how data is built 2026-04-21 18:04:02 +05:30
ruthra kumar
75a068aea8 Merge pull request #54446 from ruthra-kumar/wrong_type_hint_in_pos
fix: incorrect type hint
2026-04-21 17:56:01 +05:30
Smit Vora
6dca96b423 refactor: use consistent report column names 2026-04-21 17:28:56 +05:30
Smit Vora
f6eb844d20 Merge pull request #54422 from ljain112/fix-test-tds-report 2026-04-21 17:21:40 +05:30
Smit Vora
6d727c90b6 Merge pull request #54272 from ljain112/parrenttype 2026-04-21 17:20:56 +05:30
Smit Vora
d8fc9444ea Merge pull request #54344 from ljain112/project-filter-ar-ap 2026-04-21 17:15:37 +05:30
Mihir Kandoi
e65b9fc2ae fix: sales order is not valid when creating WO from MR from PP (#54435) 2026-04-21 09:47:02 +00:00
ruthra kumar
1995fcfdd8 fix: incorrect type hint 2026-04-21 13:57:12 +05:30
MochaMind
c2590c174d fix: sync translations from crowdin (#54358) 2026-04-21 09:24:40 +05:30
diptanilsaha
11fc3e5495 refactor: Sales Partner Commission Summary and Sales Partner Transaction Summary report (#54268) 2026-04-21 03:12:22 +00:00
Khushi Rawat
0edee23e53 Merge pull request #54131 from khushi8112/journal-entry-custom-remark-toggle
feat: use single remark field with custom remark toggle
2026-04-21 00:45:46 +05:30
Ravibharathi
f9232b209c Merge pull request #54415 from aerele/fix/clear-condions-table
fix: clear conditions table when calculate_based_on is set to Fixed
2026-04-20 19:22:25 +05:30
ravibharathi656
d6bb0ae093 fix: clear shipping rule conditions for fixed shipping rule 2026-04-20 19:01:37 +05:30
sarathibalamurugan
d73920be12 fix: clear conditions table when calculate_based_on is set to Fixed 2026-04-20 19:01:37 +05:30
ljain112
6545bcbbd9 refactor: fix test cases in tax withholding details report 2026-04-20 18:15:10 +05:30
Nihantra C. Patel
bc8f63b6dd Merge pull request #54419 from Nihantra-Patel/fix-default-letterhead-report-validation
fix: set letter_head_for letterhead and remove unknown letterhead from report
2026-04-20 17:25:51 +05:30
diptanilsaha
f4e77f63dd test(BootStrapTestData): create sales_partner test data while bootstrapping (#54416) 2026-04-20 11:41:02 +00:00
Nihantra Patel
a2ec597e3d fix: set letter_head_for for letterhead 2026-04-20 16:58:35 +05:30
diptanilsaha
6c51e4cd1f fix(pos_invoice_item): fetch grant_commission from item_code (#54413) 2026-04-20 11:21:10 +00:00
rohitwaghchaure
f89448709f Merge pull request #54350 from rohitwaghchaure/feat-backflush-based-on-in-bom
feat: backflush based on in BOM
2026-04-20 16:36:36 +05:30
Rohit Waghchaure
877d99c5a5 feat: backflush based on in BOM 2026-04-20 16:02:18 +05:30
ruthra kumar
1614d33e5c Merge pull request #54406 from ruthra-kumar/remove_dead_test_code
refactor(test): move contact and address creation to bootstrap
2026-04-20 14:16:04 +05:30
ruthra kumar
336be9d820 refactor(test): set instance variables before calling utility method 2026-04-20 13:53:03 +05:30
rohitwaghchaure
742c5f1822 Merge pull request #53708 from aerele/fetch_item_tax_template
fix: fetch get_item_tax_template while update items
2026-04-20 12:17:22 +05:30
rohitwaghchaure
0a8c195ab9 Merge pull request #54378 from rohitwaghchaure/exclude-group-warehouse
fix: exclude group warehouse in the report
2026-04-20 11:38:21 +05:30
Deepesh Garg
53d7c9fd9c Merge pull request #53756 from iamkhanraheel/fix/hr-default_role_permission
fix(hrms): default permission for HR roles
2026-04-20 11:20:12 +05:30
ruthra kumar
7f021fb705 refactor(test): move create_test_contact_and_address to bootstrap 2026-04-20 11:07:20 +05:30
ruthra kumar
e2b554e151 chore(test): remove dead test code 2026-04-20 10:37:31 +05:30
MochaMind
c1a469478d chore: update POT file (#54402) 2026-04-19 14:02:58 +02:00
Ahmed AbuKhatwa
d61b5fd5f6 fix(dashboard-trends): set default fiscal year and company before val… (#54339)
* fix(dashboard-trends): set default fiscal year and company before validating filters Ensure  and  are populated with default values

* fix(dashboard-trends): ensure fiscal_year and company are properly set before validation to avoid empty filter issues

* Update erpnext/controllers/trends.py

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 08:10:44 +00:00
Mihir Kandoi
28f3429a54 fix: recalculate operating costs if workstation type is changed (#54390)
* fix: recalculate operating costs if workstation type is changed

* fix: do not overwrite op costs on every save
2026-04-19 07:43:48 +00:00
yasmine ben ismail
af98963fa8 fix(readme): correct HTML issues and improve accessibility (#54267)
- Fix invalid width attribute (80xp → 80px)
- Remove invalid nested <p> tag in centered header section
- Add missing alt attribute to hero image for accessibility
- Improve external link security by recommending rel="noopener noreferrer"
2026-04-19 07:40:00 +00:00
Jaganath-Tridots
82438d6c72 Fix : None handling in pricing rule free item quantity calculation (#54375)
* fix(pricing_rule): handle None qty in transaction_qty calculation

* Update erpnext/accounts/doctype/pricing_rule/utils.py

---------

Co-authored-by: Jagan <jagan@DESKTOP-HPDMQ06.localdomain>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 07:39:39 +00:00
Ravibharathi
1c65cc1088 fix: validate south africa company in vat audit report (#54030)
* fix: validate south africa company in vat audit report

* fix: use qb to get invoice data

* fix: validate company region in south africa vat settings
2026-04-19 13:06:15 +05:30
Nishka Gosalia
23768ae0a5 fix: Disallow negative rates in Purchase invoice (#54254) 2026-04-19 12:55:05 +05:30
yasmine ben ismail
3d87f3c070 fix(contributing): fix typos, grammar, and inconsistent casing (#54384) 2026-04-19 07:18:12 +00:00
yasmine ben ismail
43e5dfc3ca Revise feature request template for clarity and structure (#54386)
* Revise feature request template for clarity and structure

- Updated `about` field to mention "enhancement"
- Fixed typo: "many many requests" → "many requests"
- Fixed typo: "urgent need to" → "urgent need of"
- Added "Before Submitting" checklist to reduce duplicate issues
- Improved problem statement placeholder with a role-based example
- Added Environment section for ERPNext and Frappe version info

* chore: fix case

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-19 07:17:22 +00:00
yasmine ben ismail
e6f17e0447 Fix typos and enhance phrasing in README (#54387)
Fixed typo: "80xp" → "80px" in logo image tag
- Fixed typo: "many many requests" → "many requests"
- Fixed capitalization: "Javascript" → "JavaScript"
- Fixed capitalization: "Learning and community" → "Learning and Community"
- Fixed capitalization: "Open-Source ERP system" → "Open-Source ERP System"
- Fixed capitalization: "Frappe user" → "Frappe user"
- Fixed broken documentation link to use consistent URL
- Added missing Oxford commas
- Added missing "the" before "create-site"
- Improved phrasing: "with peace of mind" → "reliably and securely"
- Improved phrasing: "ad-hoc activities" → "other daily operations"
- Improved phrasing: "already set up sandbox" → "pre-configured sandbox"
- Improved phrasing: "It takes care of" → "It handles"
2026-04-19 07:15:49 +00:00
Mihir Kandoi
d6b379b936 fix: use qty instead of stock qty dropship gross profit report (#54389) 2026-04-19 06:40:59 +00:00
Mihir Kandoi
40bcaa7bc3 fix: dropship logic should come above non stock logic in gross profit… (#54383)
fix: dropship logic should come above non stock logic in gross profit report
2026-04-18 16:16:26 +00:00
Rohit Waghchaure
31fe6a378c fix: exclude group warehouse in the report 2026-04-18 17:41:53 +05:30
Mihir Kandoi
3ef6c24f07 fix: zero valuation rate popup on SI (#54376) 2026-04-18 11:44:49 +00:00
Pandiyan P
b93f2350ee fix: fetch item tax template from item group when creating item (#54258) 2026-04-18 11:58:33 +05:30
Lakshit Jain
453fe376ab feat: add support for 'not applicable' tax in item tax templates (#50898)
* feat: add support for 'not applicable' tax in item tax templates

* refactor: remove unused imports

* fix: import NOT_APPLICABLE_TAX in get_item_tax_map function

* fix: add item wise tax details for not applicable taxes

* test: added test case for `not_applicable`

* fix: do not create item wise tax details for not applicable tax

* fix: ensure tax rate is set to 0 for not applicable tax rows

* refactor: changes as per review

* test: update selling settings

* test: correct settings

* fix: return both net and current tax amounts for not applicable tax
2026-04-18 11:34:36 +05:30
vorasmit
3c8a066484 fix: filter opening entries in first year in custom financial statement 2026-04-17 22:27:48 +05:30
rohitwaghchaure
b0fd152896 Merge pull request #54355 from aerele/fix/support-65791
fix(manufacturing): handle empty list in query builder
2026-04-17 21:21:29 +05:30
rohitwaghchaure
0359a3ed0b Merge pull request #54354 from rohitwaghchaure/fixed-negative-batch-report
fix: negative batch report showing same batch-warehouse multiple times
2026-04-17 21:10:47 +05:30
sarathibalamurugan
9eeb819106 test: add test for project name in exchange gain loss entry 2026-04-17 18:44:44 +05:30
sarathibalamurugan
d9b255b952 fix(accounts): fetch project name from payment entry to journal entry 2026-04-17 18:43:50 +05:30
Jatin3128
ba01d66c24 fix: changed qty validation from qty field to stock_qty (#54352) 2026-04-17 13:13:13 +00:00
Pandiyan37
9e5d94c1e6 fix(manufacturing): handle empty list in query builder 2026-04-17 18:26:34 +05:30
Rohit Waghchaure
700572980d fix: negative batch report showing same batch-warehouse multiple times 2026-04-17 18:21:57 +05:30
iamkhanraheel
2018a90ad8 fix: default company perms for HR manager 2026-04-17 17:13:51 +05:30
Nishka Gosalia
9ecbf57a84 Merge pull request #54074 from nishkagosalia/gh-53442 2026-04-17 16:09:44 +05:30
iamkhanraheel
d26cd69fe5 fix: remove unwanted perm for HR user role 2026-04-17 15:39:42 +05:30
MochaMind
be711eacde fix: sync translations from crowdin (#54334) 2026-04-17 11:49:12 +02:00
ljain112
0cad511136 test: add test with project not in payment entry 2026-04-17 15:18:57 +05:30
nishkagosalia
eb89903dec fix: Table row in dialog should not have delete row option 2026-04-17 15:16:40 +05:30
Nishka Gosalia
6ee4b46be0 Merge pull request #54345 from nishkagosalia/batch-form-cleanup 2026-04-17 15:13:57 +05:30
nishkagosalia
de747fe625 refactor(UX): Batch Form Cleanup 2026-04-17 14:31:42 +05:30
ljain112
d51dbf5254 fix: add project filter to accounts payable and receivable reports 2026-04-17 14:00:01 +05:30
rohitwaghchaure
f555183ab6 Merge pull request #54342 from rohitwaghchaure/fixed-reqd-fg_warehouse
fix: make Target Warehouse mandatory on UI for WO
2026-04-17 13:23:39 +05:30
Rohit Waghchaure
2a8267e10a fix: make Target Warehouse mandatory on UI 2026-04-17 13:17:06 +05:30
Mihir Kandoi
5f4641e55b fix: hide operations field in bom creator if phantom (#54336) 2026-04-16 15:36:14 +00:00
Mihir Kandoi
cac7a358dd feat: make fg phantom-able in bom creator (#54332) 2026-04-16 13:25:54 +00:00
ruthra kumar
ee50767e42 Merge pull request #54327 from ruthra-kumar/setup_default_for_repost_on_fresh_install
fix(test): missing repost allowed defaults
2026-04-16 16:02:18 +05:30
ruthra kumar
257865deb2 fix(test): missing repost allowed defaults 2026-04-16 15:36:15 +05:30
Mihir Kandoi
e04a2e6da2 refactor: add category field to uom (#54290) 2026-04-16 09:03:12 +00:00
Venkatesh
97efd51fb8 feat: add option to create production plan from sales order (#53662)
Co-authored-by: sudarsan2001 <frankel9675@gmail.com>
2026-04-16 13:49:28 +05:30
ruthra kumar
40012f6617 Merge pull request #54301 from ruthra-kumar/merge_repost_settings_to_accounts_settings
refactor(ux): merge repost settings to accounts settings
2026-04-16 12:08:50 +05:30
ruthra kumar
6a04c159ca refactor: delete redundent repost setting 2026-04-16 11:35:23 +05:30
ruthra kumar
940d3cfe0a refactor: limit reposting to only supported doctypes 2026-04-16 11:35:23 +05:30
ruthra kumar
ece85c770f refactor: remove redundant field from filter 2026-04-16 11:35:23 +05:30
ruthra kumar
3093409933 refactor(ux): better error message 2026-04-16 11:35:23 +05:30
ruthra kumar
b8207d5ed1 refactor(test): use new source for repost setting 2026-04-16 11:35:23 +05:30
ruthra kumar
d5c58277cb refactor: move allowed doctypes to accounts settings
- dropped 'allowed' field
2026-04-16 11:35:20 +05:30
ruthra kumar
ff2d536943 Merge pull request #54172 from Shllokkk/acc-dimension-fix
fix: move make_dimension_in_accounting_doctypes from after_insert to on_update
2026-04-16 11:05:51 +05:30
ruthra kumar
2f4bb23125 Merge pull request #52923 from aerele/fix/taxable-amount-conversion-rate
fix(taxes_and_totals): apply conversion_rate to taxable_amount in get_itemised_tax
2026-04-16 10:52:13 +05:30
Dharanidharan2813
2e577ed25b fix(taxes_and_totals): apply conversion_rate to taxable_amount in get_itemised_tax 2026-04-16 10:30:20 +05:30
MochaMind
2e1d426c78 fix: sync translations from crowdin (#54312) 2026-04-15 16:11:43 +00:00
NaviN
9e9c8a07a7 Merge pull request #54306 from aerele/fix/expand_details_in_customer_quick_entry
fix: non-collapsible in customer quick entry
2026-04-15 17:26:13 +05:30
PKSowmiya05
53e120269d fix: non-collapsible in customer quick entry 2026-04-15 17:17:51 +05:30
ruthra kumar
89ebf48544 refactor: merge reposting settings to accounts settings 2026-04-15 16:31:02 +05:30
ruthra kumar
4198dd643d refactor(company): don't force set service expense account on save (#54275) 2026-04-15 15:33:39 +05:30
Smit Vora
ab61a757e3 Merge pull request #54241 from ljain112/fix-rounded-total 2026-04-15 15:23:14 +05:30
ruthra kumar
299e141cee refactor(test): set dependant value in company master 2026-04-15 12:29:29 +05:30
Mihir Kandoi
af6974893b fix: add portal user ownership check to supplier quotation (#54298) 2026-04-15 11:21:08 +05:30
rohitwaghchaure
0969ec4186 Merge pull request #54279 from rohitwaghchaure/fixed-banner-for-serial-batch
fix: banner to enable serial / batch feature
2026-04-14 23:14:30 +05:30
Vishnu Priya Baskaran
ce2670b252 Revert "fix: sync paid and received amount" (#54238) 2026-04-14 22:16:00 +05:30
MochaMind
b083121421 fix: sync translations from crowdin (#54259) 2026-04-14 16:46:06 +02:00
iamkhanraheel
41103a0622 fix: default perm for HR manager & HR user 2026-04-14 18:30:41 +05:30
Rohit Waghchaure
08e8cc8575 fix: banner to enable serial / batch feature 2026-04-14 18:26:35 +05:30
Sudharsanan Ashok
b6b7e8e2f6 fix(stock): remove float precision to fix precision issue (#54284) 2026-04-14 16:42:17 +05:30
Mihir Kandoi
1cc2b159dd fix: handle multi uom conversion factor for manufacture entry (#54285) 2026-04-14 10:43:47 +00:00
Mihir Kandoi
5ff2ae5a83 fix: fetch correct expense account for operations in stock entry (#54278) 2026-04-14 09:55:07 +00:00
Mihir Kandoi
ea0d53e2f3 fix: add drop ship logic in gross profit report (#54220) 2026-04-14 09:29:01 +00:00
ruthra kumar
927f40b296 refactor(company): don't force set service expense account on save 2026-04-14 14:46:46 +05:30
Mihir Kandoi
f37bf62824 fix: wrong operation time calculation (#53796) 2026-04-14 14:42:02 +05:30
ljain112
3aeb7d6b01 fix(purchase_register): filter tax rows by parenttype in invoice tax map query 2026-04-14 12:31:10 +05:30
Raffael Meyer
8a72d7fafe fix(edi): restrict Code List imports to files and trusted backend URLs (#54137) 2026-04-13 21:44:24 +05:30
Sudharsanan Ashok
2f025272d7 fix(stock): update bin to zero when no previous sle exists (#54236) 2026-04-13 21:04:14 +05:30
rohitwaghchaure
488747eb5d Merge pull request #54257 from rohitwaghchaure/fixed-github-65007
fix: not able to submit the PO
2026-04-13 19:43:34 +05:30
Rohit Waghchaure
1e43c37452 fix: not able to submit the PO 2026-04-13 17:10:37 +05:30
ljain112
e2ac476587 chore: spelling mistake 2026-04-13 16:51:20 +05:30
Khushi Rawat
6c5788dfba Merge pull request #54190 from khushi8112/company-address-permission-check
fix: add permission validation when prompting company details for incomplete letterhead data
2026-04-13 15:24:04 +05:30
Nishka Gosalia
aa9d35b28a Merge pull request #54249 from nishkagosalia/gh-53595 2026-04-13 15:21:30 +05:30
khushi8112
84e5272f5d fix: append row level user remarks in gl map 2026-04-13 15:15:52 +05:30
khushi8112
697f521e14 feat: use single remark field with custom remark toggle 2026-04-13 15:15:45 +05:30
Khushi Rawat
c805324a99 Merge pull request #54244 from khushi8112/journal-entry-get-against-jv-sql-injection
fix: replace raw SQL with qb in get_against_jv to prevent SQL injection
2026-04-13 15:12:49 +05:30
nishkagosalia
3e2b40ad4a refactor(UX): Stock ledger serial and batch number fields 2026-04-13 14:47:22 +05:30
khushi8112
c133f7156d fix: replace raw SQL with qb in get_against_jv to prevent SQL injection 2026-04-13 14:47:21 +05:30
Sudarshan
a9bb3e2315 fix: make operation mandatory when any sub operation row is added (#54245) 2026-04-13 07:38:22 +00:00
Khushi Rawat
577a7591c7 Merge pull request #54176 from khushi8112/payment-entry-list-reconciliation-indicator
feat: show reconciled/unreconciled indicator in list view
2026-04-13 12:13:23 +05:30
ljain112
f8d278b733 fix: reset base_rounded_total when rounded_total resets 2026-04-13 11:48:08 +05:30
ruthra kumar
451e4fbb21 Merge pull request #54237 from ruthra-kumar/bold_group_accounts
refactor: boldface for group accounts in financial statements
2026-04-13 11:40:23 +05:30
ruthra kumar
545e9e069a refactor: boldface for group accounts in financial statements 2026-04-13 11:16:49 +05:30
MochaMind
3617a9b674 fix: sync translations from crowdin (#54234) 2026-04-12 19:11:36 +02:00
MochaMind
39d93f35e0 chore: update POT file (#54228) 2026-04-12 10:12:49 +00:00
Shllokkk
44e0b36093 fix: minor changes in print templates 2026-04-12 13:14:24 +05:30
Shllokkk
915fcc0166 fix: minor changes in print template 2026-04-12 13:07:41 +05:30
Shllokkk
e3019c827c fix: minor changes in print template 2026-04-12 13:00:51 +05:30
MochaMind
a76336e3d9 fix: sync translations from crowdin (#54181) 2026-04-11 21:07:20 +02:00
Shllokkk
e8d08df044 fix: changes to gl print template 2026-04-11 23:06:01 +05:30
mgicking-bmi
3e5d18c5c4 Fix(selling): enable selling_settings creation through fixtures (#54177) 2026-04-11 05:12:00 +00:00
Mihir Kandoi
2f5fa3b207 fix: batch/serial should use parent's posting datetime for naming (#54206) 2026-04-10 18:59:16 +00:00
Sambhav Saxena
1dcfd9174f Fix(bom): refetch the rate of item when 'source_from_supplier' is updated (#54187) 2026-04-10 18:12:13 +00:00
rohitwaghchaure
1e64f392bb Merge pull request #54182 from nishkagosalia/st-64901-2
fix: account change in warehouse
2026-04-10 19:41:14 +05:30
nishkagosalia
777d9161cc fix: account change in warehouse 2026-04-10 18:54:43 +05:30
Praveenkumar Dhanasekar
887d2a8379 fix: update return value in workstation list view indicator (#54198) 2026-04-10 16:49:05 +05:30
Mihir Kandoi
9cdfe74de6 fix: remove unneccessary function for serial no status updation (#54191) 2026-04-10 10:36:42 +00:00
Trusted Computer
bd9427623f refactor: bring back titles on sales transactions and make them optional and visible on purchase transactions (#52633)
* fix: correct wrong PO titles

* refactor: restore title fields to sales transaction doctypes

* refactor: change title fields to optional fields with no default in purchase transactional doctypes

* chore: re-save doctype definitions

- updates modified timestamps
- regenerates type hints

---------

Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2026-04-10 13:09:32 +05:30
khushi8112
256a258b38 fix: add permission validation when prompting company details for incomplete letterhead data 2026-04-10 12:53:45 +05:30
iamkhanraheel
f02b3b6166 fix: default perm for HR manager & HR user 2026-04-09 19:15:45 +05:30
Mihir Kandoi
9bc5a30ea4 Revert "fix: update_nsm only in warehouse creation" (#54178) 2026-04-09 13:04:38 +00:00
khushi8112
a48a29410e fix: refresh after unreconcile 2026-04-09 18:05:39 +05:30
khushi8112
7eded60892 feat: show reconciled/unreconciled indicator in list view 2026-04-09 17:40:03 +05:30
Shllokkk
ee067e6015 fix: move make_dimension_in_accounting_doctypes from after_insert to on_update 2026-04-09 16:27:54 +05:30
Nishka Gosalia
b0e3fa3979 fix: update_nsm only in warehouse creation (#54165) 2026-04-09 10:27:22 +00:00
Khushi Rawat
514c86cf4b Merge pull request #54142 from khushi8112/blank-remarks-in-invoices
fix: Set remarks blank instead of No remarks in Sales/Purchase Invoices
2026-04-09 14:54:23 +05:30
khushi8112
56416d18d3 fix(test): Remove usage of No remark as remark in tests 2026-04-09 14:23:52 +05:30
rohitwaghchaure
90a1d32098 Merge pull request #54161 from rohitwaghchaure/fixed-posting-time-riv
fix: set default posting time in RIV
2026-04-09 13:55:44 +05:30
Rohit Waghchaure
a7ece65536 fix: set default posting time in RIV 2026-04-09 13:31:34 +05:30
Aarol D'Souza
f5dda90f26 Merge pull request #54129 from AarDG10/refactor-util-use
refactor: update reset password method name
2026-04-09 11:52:15 +05:30
mergify[bot]
f09001a25e Merge branch 'develop' into refactor-util-use 2026-04-09 05:58:56 +00:00
rohitwaghchaure
d7254bba47 Merge pull request #54132 from rohitwaghchaure/fixed-reposting-file-not-updating
fix: last SLE not updated in the file
2026-04-09 08:22:11 +05:30
Mihir Kandoi
71a17cfda9 fix: inventory dimension patch (#54147) 2026-04-09 02:26:35 +00:00
Mihir Kandoi
7f0751539b fix: inventory dimension patch (#54141) 2026-04-09 01:45:48 +00:00
Nishka Gosalia
7ef48a966a feat: Allowing operation level quality inspection check in BOM (#53859)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-09 01:41:45 +00:00
khushi8112
2515bf3aff fix: Set remarks blank instead of No remarks in Sales/Purchase Invoices 2026-04-09 01:54:35 +05:30
diptanilsaha
d15cd08e72 refactor(lost_opportunity_report): replaced raw_sql with query builder (#54136) 2026-04-08 23:38:43 +05:30
Rohit Waghchaure
38ed425ee2 fix: last SLE not updated in the file 2026-04-08 20:43:23 +05:30
NaviN
ef454822d7 fix(sales invoice): toggle Get Items From button based on is_return and POS view (#52594) 2026-04-08 20:42:58 +05:30
Mihir Kandoi
6e44b8913e fix: inventory dimensions should not be mandatory unnecesarily (#54064) 2026-04-08 19:09:56 +05:30
Sudharsanan Ashok
9d16d06504 fix(manufacturing): check remaining qty to calculate operating cost (#53983) 2026-04-08 17:20:49 +05:30
Nishka Gosalia
31319cb6ee fix: quality inspection item code fetch perm issue (#54121)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-04-08 17:20:09 +05:30
AarDG10
c4d74483e1 refactor: update reset password method name 2026-04-08 17:19:43 +05:30
Sudharsanan Ashok
086122f650 fix(stock): ignore delivery note on delivery trip on_cancel trigger (#54120) 2026-04-08 16:36:02 +05:30
rohitwaghchaure
f9e2696745 Merge pull request #54102 from rohitwaghchaure/fixed-precision-issue
fix: hardcoded precision causing decimal issues
2026-04-08 12:19:21 +05:30
Khushi Rawat
f183885829 Merge pull request #54103 from aerele/asset-movement-field-state-after-save
fix: preserve asset movement field properties after save
2026-04-08 11:57:19 +05:30
MochaMind
eb04706fee fix: sync translations from crowdin (#54105) 2026-04-07 23:12:50 +05:30
Abdeali Chharchhodawala
76a7781283 refactor: financial report template enhancements (#52687) 2026-04-07 22:18:31 +05:30
Ahmed AbuKhatwa
89560d4691 fix(promotional_scheme): toggle enable state between Buying and Selli… (#54110)
Co-authored-by: AhmedAbukhatwa <Ahmedabukhatwa1@gmail.com>
2026-04-07 21:52:24 +05:30
Rohit Waghchaure
90fd6f2e40 fix: hardcoded precision causing decimal issues 2026-04-07 19:48:18 +05:30
Vishnu Priya Baskaran
edba5f3a06 fix: sync paid and received amount (#53039) 2026-04-07 18:33:31 +05:30
mergify[bot]
6da5dff26c fix: skip validate_stock_accounts in Journal Entry when perpetual inventory is disabled (backport #53554) (backport #53558) (#54104)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Saeed Kola <mohammedsaeedk@gmail.com>
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-04-07 12:28:41 +00:00
ravibharathi656
4a004a2a82 fix: preserve asset movement field properties after save 2026-04-07 16:54:44 +05:30
Smit Vora
187542bfa5 Merge pull request #53964 from vorasmit/disassembly-by-se 2026-04-07 14:17:16 +05:30
Smit Vora
98dfd64f63 fix: remove unnecessary param, and use value from self 2026-04-07 13:17:22 +05:30
Shllokkk
4228885f1e fix: minor bug fixes for ar print template 2026-04-07 13:01:18 +05:30
Shllokkk
e6a32a9d02 feat: introduce print format for Accounts Receivable report 2026-04-07 13:01:18 +05:30
Shllokkk
ffc59ebc9c fix: improve design and refactor ar print template 2026-04-07 13:01:18 +05:30
Khushi Rawat
b4be235bbf Merge pull request #53394 from aerele/fix-asset-filter
fix: remove null from link_filters
2026-04-07 12:29:44 +05:30
Sakthivel Murugan S
00c94afa78 fix: task gantt popup text not visible in light theme (#53882) 2026-04-07 12:08:52 +05:30
Mihir Kandoi
17853931d6 fix: divide sub-assembly cost by qty to get per-unit rate in BOM Creator (#54090)
Co-authored-by: Ravindu Gajanayaka <ravindu2012@users.noreply.github.com>
2026-04-07 05:33:05 +00:00
Smit Vora
f13d37fbf9 test: enhance tests as per review comments 2026-04-07 10:54:34 +05:30
Smit Vora
b892139342 test: maintain sufficient stock for scrap item 2026-04-07 10:27:41 +05:30
Smit Vora
ab1fc22431 fix: set bom details on disassembly; abs batch qty 2026-04-07 10:00:30 +05:30
Smit Vora
93ad48bc1b fix: process loss with bom path disassembly 2026-04-07 09:59:54 +05:30
Amine
13cc07237e docs: fix typo, grammar and numbering in README.md (#54008) 2026-04-07 09:41:16 +05:30
mahsem
4282b9c68a fix: dif_inward_from_outward_workspace_sidebar (#54083) 2026-04-07 09:26:23 +05:30
Lakshit Jain
6d11a08cdd fix: add tax_id handling in Tax Withholding Entry (#53598)
* fix: add tax_id handling in Tax Withholding Entry

* fix: update get_tax_id_for_party to handle absence of tax_id in payment and journal entries

* fix: refactor tax_id handling in Tax Withholding Entry and Details

* test: correct function address
2026-04-06 22:27:32 +05:30
Nishka Gosalia
66780543bd fix: transactions where update stock is 0 should not create SLEs (#54035) 2026-04-06 20:25:51 +05:30
Smit Vora
ea392b2009 fix: validate work order consistency in stock entry 2026-04-06 20:08:38 +05:30
MochaMind
80272de0df fix: sync translations from crowdin (#53955) 2026-04-06 13:07:08 +00:00
Smit Vora
54342539c3 Merge pull request #53973 from vorasmit/persist-toggle 2026-04-06 17:26:19 +05:30
rohitwaghchaure
eebc0d2ad3 Merge pull request #54050 from rohitwaghchaure/fixed-github-51874
fix: GL entries for different exchange rate in the purchase invoice
2026-04-06 17:21:50 +05:30
Amine
fa814c0b71 fix: fix formatting and missing bullet point in TRADEMARK_POLICY.md (#54054) 2026-04-06 17:13:04 +05:30
Amine
83235b90d7 fix: fix incorrect issue link numbers in sponsors.md (#54055) 2026-04-06 17:12:26 +05:30
Mihir Kandoi
9b60d8e711 fix: remove title field from purchase receipt (#54051) 2026-04-06 17:09:00 +05:30
Rohit Waghchaure
a953709640 fix: GL entries for different exchange rate in the purchase invoice 2026-04-06 17:00:02 +05:30
Khushi Rawat
adea316dd2 Merge pull request #54052 from khushi8112/add-print-hide-to-fields
fix: print hide unnecessary fields
2026-04-06 16:13:26 +05:30
Deepesh Garg
77578e41e5 Merge pull request #54033 from krishna-254/fix-user-permission-error-on-status-change
fix: resolve user permission error on status change by updating user …
2026-04-06 15:49:47 +05:30
mahsem
1e90b9a148 feat: croatian_address_template (#53888) 2026-04-06 15:41:23 +05:30
Krishna Shirsath
c6695b613c fix: resolve user permission error on status change by updating user enabled status directly 2026-04-06 15:27:39 +05:30
khushi8112
8f83616b60 fix: print hide unnecessary fields 2026-04-06 14:35:15 +05:30
Sagar Vora
99da4f5147 Merge pull request #54042 from sagarvora/fix/discount-amount-validation-on-save
fix: skip discount amount validation when not saving
2026-04-06 13:00:22 +05:30
Sagar Vora
135cb5fd67 test: add test for discount amount on partial purchase receipt
Co-authored-by: ravibharathi656 <131471282+ravibharathi656@users.noreply.github.com>
2026-04-06 12:38:46 +05:30
Sagar Vora
0975583388 fix: skip discount amount validation when not saving 2026-04-06 12:33:49 +05:30
Gajendra Nishad
b9ef061911 fix: show current stock qty in Stock Entry PDF (#53761) 2026-04-06 10:50:13 +05:30
Amine
7a2759b2f0 fix: fix typos, grammar and numbering in CONTRIBUTING.md (#54027) 2026-04-06 04:25:24 +00:00
rohitwaghchaure
ccd017c737 Merge pull request #54004 from rohitwaghchaure/fixed-do-not-repost-gl
fix: do not repost GL if no change in valuation
2026-04-06 09:07:29 +05:30
Rohit Waghchaure
bb53cce228 fix: do not repost GL if no change in valuation 2026-04-05 22:54:51 +05:30
Vishnu Priya Baskaran
9417f55b7d fix: update min date based on transaction_date (#53803) 2026-04-05 20:59:13 +05:30
MochaMind
eca3ec114c chore: update POT file (#54018) 2026-04-05 15:59:23 +02:00
rohitwaghchaure
92dc95570d Merge pull request #54005 from rohitwaghchaure/fixed-github-53991
fix: screen freezes if consumed qty set in SCR
2026-04-05 13:19:49 +05:30
Pandiyan P
74b11710cc fix(manufacturing): handle null cur_dialog in BOM work order dialog (#54011) 2026-04-05 12:47:18 +05:30
ervishnucs
ad22256b2d fix: resolve item tax template from item group in update items 2026-04-05 11:16:37 +05:30
Rohit Waghchaure
dd7be2b370 fix: screen freezes if consumed qty set in SCR 2026-04-04 13:25:43 +05:30
rohitwaghchaure
f322dc9d69 Merge pull request #53994 from aerele/fix/update-sabe-stock-queue
fix(stock): update stock queue in SABE for return entries
2026-04-04 12:51:34 +05:30
kavin-114
e537896df8 test(stock): add unit test to update stock queue for return 2026-04-03 17:37:19 +05:30
kavin-114
0af8077bcc fix(stock): update stock queue in SABE for return entries 2026-04-03 17:37:05 +05:30
vorasmit
a71e8bb116 fix: use get_value 2026-04-03 16:19:43 +05:30
Mihir Kandoi
efd716e53d fix: remove reference in serial/batch when document is cancelled (#53979) 2026-04-02 07:51:20 +00:00
vorasmit
71fd18bdf9 fix: avg stock entries for disassembly from WO 2026-04-01 23:56:44 +05:30
vorasmit
3cf1ce8360 fix: manufacture entry with group_by support 2026-04-01 23:42:36 +05:30
Smit Vora
a6d41151ff test: disassembly for scrap / secondary item 2026-04-01 16:55:15 +05:30
Smit Vora
2be8313819 fix: handle disassembly for secondary / scrap items 2026-04-01 16:55:15 +05:30
Smit Vora
1693698fed test: disassembly of items with batch and serial numbers 2026-04-01 16:55:15 +05:30
Smit Vora
d32977e3a9 test: additional items in stock entry considered with disassembly 2026-04-01 16:55:15 +05:30
Smit Vora
6988e2cbbc test: disassemble with source stock entry reference 2026-04-01 16:55:15 +05:30
Smit Vora
342a14d340 test: disassembly from wo 2026-04-01 16:55:15 +05:30
Smit Vora
13b019ab8e fix: set serial and batch from source stock entry - on disassemble 2026-04-01 16:55:15 +05:30
Smit Vora
d3d6b5c660 fix: correct warehouse preference for disassemble 2026-04-01 16:55:15 +05:30
Smit Vora
2e4e8bcaa7 fix: auto-set source_stock_entry 2026-04-01 16:55:15 +05:30
Smit Vora
1ed0124ad7 fix: add support to fetch items based on manufacture stock entry; fix how it's done from work order 2026-04-01 16:55:15 +05:30
Smit Vora
6394dead72 fix: validate qty that can be disassembled from source stock entry. 2026-04-01 16:55:15 +05:30
Smit Vora
dba82720b6 fix: support creating disassembly (without link of WO) 2026-04-01 16:55:15 +05:30
Smit Vora
b64f86148c fix: custom button to disassemble manufactured stock entry with work order 2026-04-01 16:55:15 +05:30
Smit Vora
b47dfacb3e fix: set_query for source stock entry 2026-04-01 16:55:15 +05:30
Smit Vora
68e97808c5 fix: disassembly prompt with source stock entry field 2026-04-01 16:55:14 +05:30
Smit Vora
d4baa9a74a fix: create source_stock_entry to refer to original manufacturing entry 2026-04-01 16:55:13 +05:30
rohitwaghchaure
7846548a1b Merge pull request #53963 from rohitwaghchaure/fixed-github-53743
fix: hide fields related to track Semi-Finished Goods if feature has disabled
2026-04-01 16:37:13 +05:30
Shllokkk
86ee9959a2 fix: minor bugs in print templates 2026-04-01 12:32:15 +05:30
Rohit Waghchaure
399faf0ced fix: hide fields related to track Semi-Finished Goods if feature has disabled 2026-04-01 12:05:41 +05:30
Smit Vora
da778edf48 fix(ux): refresh grid to correctly persist the state of fields 2026-04-01 08:43:56 +05:30
Shllokkk
fbe5d128a8 feat: sticky column in various reports (#53960)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
Co-authored-by: mihir-kandoi <kandoimihir@gmail.com>
2026-03-31 23:38:19 +05:30
Mihir Kandoi
d76ddf7271 fix: include rejected qty in tax (purchase receipt) (#53624) 2026-03-31 15:29:56 +00:00
Lakshit Jain
1e85d72127 Merge pull request #53961 from ljain112/fix-taxe-rounding
fix: ensure accurate rounding for item-wise tax and taxable amounts
2026-03-31 19:49:39 +05:30
Mihir Kandoi
ef18b5cd93 Revert "chore: initiate release twice in a week" (#53944) 2026-03-31 19:01:12 +05:30
Nishka Gosalia
7ff3dc0ac4 Merge pull request #53965 from nishkagosalia/gh-53962 2026-03-31 18:07:43 +05:30
nishkagosalia
e9e510a76e fix: Party Field only visibile when party type selected 2026-03-31 17:52:41 +05:30
ljain112
b73b161cbe test: improve test case 2026-03-31 17:46:46 +05:30
ljain112
9b37f2d95c fix: ensure accurate rounding for item-wise tax and taxable amounts 2026-03-31 17:23:01 +05:30
Khushi Rawat
f5bf95ca65 Merge pull request #53811 from khushi8112/customer-group-is-group-validation
fix: prevent selection of group type customer group in customer master
2026-03-31 16:46:25 +05:30
rohitwaghchaure
4013092271 Merge pull request #53953 from rohitwaghchaure/fixed-github-53597
fix: rejected serial no field showing even if serial / batch feature disabled
2026-03-31 16:11:12 +05:30
Rohit Waghchaure
c2f419ac3d fix: rejected serial no field showing even if serial / batch feature not enabled 2026-03-31 16:09:00 +05:30
Ejaaz Khan
eaa4f7bb55 Merge pull request #53949 from iamejaaz/sticky-erpnext-reports
feat: sticky columns in reports
2026-03-31 16:04:31 +05:30
khushi8112
75fa2b2277 fix(test): do not use is_group enabled customer group in test 2026-03-31 15:42:08 +05:30
Ejaaz Khan
df753676c6 fix: semgrep translation issue 2026-03-31 15:34:46 +05:30
Ejaaz Khan
03e4df7a1a feat: sticky columns in reports
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-03-31 15:12:58 +05:30
Mihir Kandoi
f1529a05b2 fix: do not show inv dimension unnecessarily in stock entry (#53946) 2026-03-31 09:42:21 +00:00
ruthra kumar
f452ad3ce2 Merge pull request #53795 from ruthra-kumar/use_erpnext_testsuite_across_repo
refactor(test): enforce ERPNextTestSuite across repo
2026-03-31 15:01:58 +05:30
khushi8112
6068dc959f fix: prevent selection of group type customer group in customer master 2026-03-31 14:40:59 +05:30
Khushi Rawat
dce5e46599 Merge pull request #53939 from khushi8112/ux-improvement-for-opening-invoice-tool
fix: dynamic labels on invoice type change
2026-03-31 12:06:45 +05:30
Nishka Gosalia
0696bd2082 chore: remove inter warehouse transfer settings (#53860) 2026-03-31 11:13:26 +05:30
khushi8112
820bd15e1e fix: dynamic labels on invoice type change 2026-03-31 01:53:28 +05:30
MochaMind
e7614e2290 fix: sync translations from crowdin (#53864) 2026-03-30 18:11:38 +00:00
Ankush Menat
f97877a60a fix(UX): Store weekly off at the end of holiday list (#53833)
Perhaps these two should be stored separately too?
2026-03-30 21:28:40 +05:30
Smit Vora
28ac0effff Merge pull request #53925 from ljain112/fix-item-tax-rounding 2026-03-30 20:46:21 +05:30
ljain112
fc8437c499 test: update item-wise tax detail test for high conversion rates 2026-03-30 20:04:53 +05:30
Smit Vora
a9edd3f132 Merge pull request #53406 from ljain112/fix-item-valaution-deduct 2026-03-30 19:53:55 +05:30
Smit Vora
a18196f584 fix(taxes): improve tax calculation accuracy and update test assertions 2026-03-30 19:37:59 +05:30
diptanilsaha
7d7a1efadb fix(bank_account): added validation to fetch bank account details using get_bank_account_details (#53926) 2026-03-30 18:51:27 +05:30
ljain112
3449ab063a fix(tests): update item code and quantity in tax detail test case 2026-03-30 18:37:07 +05:30
Jatin3128
d827ab3d2e Merge pull request #53922 from Jatin3128/toggle-ps
feat(Payment Request): Added a toggle for using the payment schedule
2026-03-30 18:28:30 +05:30
Jatin3128
8ec15b537e feat(Payment Request): Added a toggle for using the payment schedule 2026-03-30 18:05:42 +05:30
ljain112
7f87a5e5c6 fix(taxes): increase rounding threshold for tax breakup calculations 2026-03-30 17:58:47 +05:30
diptanilsaha
c41730dfee fix(opening_invoice_creation_tool): sanitize summary content for dashboard (#53917) 2026-03-30 17:52:27 +05:30
diptanilsaha
fa5238ba12 fix(item_dashboard): escaping warehouse, item_code, stock_uom and item_name on get_data (#53904) 2026-03-30 09:30:51 +00:00
rohitwaghchaure
b9f26a1f31 Merge pull request #53906 from rohitwaghchaure/fixed-support-63613
fix: purchase invoice missing item
2026-03-30 14:44:37 +05:30
diptanilsaha
eda64cbd4d fix(warehouse_capacity_dashboard): removed escape from template (#53907) 2026-03-30 09:01:34 +00:00
Rohit Waghchaure
af994c1a22 fix: purchase invoice missing item 2026-03-30 14:07:31 +05:30
rohitwaghchaure
91aaabdd31 Merge pull request #53902 from rohitwaghchaure/fixed-code-cleanup-reposting
fix: item-wh reposting, code cleanup
2026-03-30 14:01:07 +05:30
Rohit Waghchaure
e0ca34ae39 fix: item-wh reposting, code cleanup 2026-03-30 13:39:24 +05:30
diptanilsaha
ddeb9775ed fix(warehouse_capacity_dashboard): escaping warehouse, item_code and company on get_data (#53894) 2026-03-30 07:46:33 +00:00
Sudharsanan Ashok
ad25c6d163 fix(stock): add warehouse filter to pick work order raw materials (#53748) 2026-03-30 13:15:48 +05:30
rohitwaghchaure
71a1dda958 Merge pull request #53799 from aerele/fix/lcv-company-validation
fix(stock): update company validation for expense account in lcv
2026-03-30 13:03:36 +05:30
Sudharsanan11
875a2e4947 fix(test): enable perpetual inventory 2026-03-30 12:16:48 +05:30
Sudharsanan Ashok
d28474a450 fix(stock): ignore qty validation for pick list (#53871) 2026-03-30 06:37:26 +00:00
Sudharsanan Ashok
f3a794384a fix(manufacturing): update the qty precision (#53874) 2026-03-29 21:51:36 +05:30
ervishnucs
97e7916b66 fix: resolve item tax template from item group in update items 2026-03-29 21:35:26 +05:30
MochaMind
893eb8c77a chore: update POT file (#53876) 2026-03-29 14:56:50 +02:00
rohitwaghchaure
0e0a7f3563 Merge pull request #53878 from rohitwaghchaure/fixed-maintain-reposting-state
fix: maintain state during reposting
2026-03-29 16:12:48 +05:30
Rohit Waghchaure
f8738a791b fix: maintain state during reposting 2026-03-29 15:53:46 +05:30
Kaushal Shriwas
6badf00313 fix: change shipment parcel dimension fields from Int to Float (#53867) 2026-03-29 06:48:51 +00:00
Shllokkk
5bbcb73808 fix: revamp print formats for accounts receivable summary and accounts payable summary reports 2026-03-29 02:02:59 +05:30
diptanilsaha
998469d5c7 refactor: setup wizard stages and demo data creation (#53866) 2026-03-29 00:43:58 +05:30
rohitwaghchaure
5c95f3347b Merge pull request #53853 from rohitwaghchaure/fixed-reposting-dependent-sles
fix: corrected logic to retry reposting if timeout occurs after dependant SLE processing
2026-03-27 21:25:44 +05:30
Rohit Waghchaure
90b9ab0bc8 fix: corrected logic to retry reposting if timeout occurs after dependent SLE processing 2026-03-27 21:04:35 +05:30
Mihir Kandoi
935eea6463 fix: invalid dynamic link filter for address doctype (#53849) 2026-03-27 12:23:58 +00:00
Shllokkk
2bf9d41797 feat: add print format for accounts payable report 2026-03-27 16:03:20 +05:30
Mihir Kandoi
6008fa710d fix: validate if quantity greater than 0 in item dashboard (#53846) 2026-03-27 10:32:35 +00:00
ruthra kumar
47bb728f65 fix: sync translations from crowdin (#53841)
* fix: Italian translations

* fix: Swedish translations
2026-03-27 15:56:27 +05:30
MochaMind
aef6c959ea fix: Swedish translations 2026-03-27 14:12:13 +05:30
MochaMind
bddc7a3e4a fix: Italian translations 2026-03-27 14:11:56 +05:30
rohitwaghchaure
54c6948174 Merge pull request #53839 from rohitwaghchaure/fixed-job-card-timer-issue
fix: timer not showing in job card
2026-03-27 13:50:14 +05:30
ruthra kumar
e45af4345f Merge pull request #53837 from ruthra-kumar/semgrep_to_prevent_test_regression
ci: semgrep to prevent test regression
2026-03-27 13:41:47 +05:30
Rohit Waghchaure
58dbb3d638 fix: timer not showing in job card 2026-03-27 13:06:34 +05:30
ruthra kumar
be4496e4ab ci: semgrep to prevent test regression 2026-03-27 12:49:28 +05:30
Shllokkk
c051536182 refactor: revamp print template for accounts payable report 2026-03-27 12:32:17 +05:30
ruthra kumar
2aecf0103a refactor(test): remove AccountsTestMixin from Sales Order 2026-03-27 12:12:48 +05:30
rohitwaghchaure
e6cac26640 Merge pull request #53812 from rohitwaghchaure/fixed-pick-correct-dependent-sle
fix: pick correct dependant sle during reposting
2026-03-27 12:12:48 +05:30
ruthra kumar
d2ee967383 refactor(test): remove AccountsTestMixin from reactivity 2026-03-27 11:56:52 +05:30
ruthra kumar
0b6546ea06 refactor(test): remove AccountsTestMixin from distributed discount 2026-03-27 11:56:30 +05:30
Rohit Waghchaure
8e8ee56e64 fix: pick correct dependant sle during reposting 2026-03-27 11:50:34 +05:30
ruthra kumar
13505ddcfb test: fixed test case (backport #53826) (#53834)
test: fixed test case

(cherry picked from commit 10f58112ae)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2026-03-27 11:47:12 +05:30
Nishka Gosalia
5d4ac95e7a Merge pull request #53704 from nishkagosalia/gh-53512-fixes 2026-03-27 11:38:52 +05:30
ruthra kumar
2b37d7514d refactor(test): move logic from AccountsTestMixin to ERPNextTestSuite 2026-03-27 11:20:11 +05:30
Rohit Waghchaure
8368feb9df test: fixed test case
(cherry picked from commit 10f58112ae)
2026-03-27 05:49:25 +00:00
nishkagosalia
3a78af7f42 fix: test case 2026-03-27 11:11:19 +05:30
nishkagosalia
3bedc6cf7e chore: Dropping bom stock report and bom stock calculated report 2026-03-27 11:11:18 +05:30
nishkagosalia
c1874cb7d5 fix: change in functionality 2026-03-27 11:11:18 +05:30
nishkagosalia
5d088350dc feat: Bom stock analysis report 2026-03-27 11:11:18 +05:30
ruthra kumar
f3148e052c refactor(test): erpnext testsuite should be primary superclass 2026-03-27 11:00:25 +05:30
Shllokkk
d987688058 fix: support translated search in get_party_type and refactor raw sql to qb 2026-03-27 10:49:34 +05:30
ruthra kumar
c283c1c472 Merge pull request #53074 from Jatin3128/subscription-section-hide
feat: add setting to hide Subscription references across doctypes
2026-03-27 10:37:59 +05:30
ruthra kumar
6566acbe23 Merge pull request #53429 from Shllokkk/deferred-rev-fix
feat(report): add service start/end date and amount with roll-ups in deferred revenue/expense report
2026-03-27 10:30:12 +05:30
ruthra kumar
d6755c3d14 Merge pull request #53343 from Shllokkk/email-campaign-fix
fix(email_campaign): prevent unsubscribing entire campaign when email group member unsubscribes
2026-03-27 10:26:49 +05:30
Shllokkk
0d4f56bf84 refactor: table body data rendering cleanup 2026-03-26 21:56:51 +05:30
Pandiyan P
5b1fa81451 fix(accounts): set supplier name as title field in Purchase Invoice (#53710)
fix(accounts): update title field in purchase order and purchase invoice
2026-03-26 19:00:10 +05:30
Mihir Kandoi
8164d195fc fix: flaky currency exchange test (#53813) 2026-03-26 12:23:55 +00:00
iamkhanraheel
5ec66169a7 fix: default permission for HR manager role 2026-03-26 17:23:54 +05:30
Shllokkk
9660debe28 fix: improve filter details render logic to avoid showing duplicate information 2026-03-26 16:18:51 +05:30
rohitwaghchaure
f382b30b4e Merge pull request #53216 from aerele/fix/legacy-recon-in-ageing
fix(stock): handle legacy single sle recon entries
2026-03-26 15:37:21 +05:30
rohitwaghchaure
15996952f6 Merge pull request #52152 from rohitwaghchaure/refactor-reposting-feature
Refactor reposting feature
2026-03-26 15:12:20 +05:30
Rohit Waghchaure
daa2420996 refactor: storing of current status of reposting 2026-03-26 14:49:39 +05:30
Krishna Pramod Shirsath
f37b6fde72 Merge pull request #53245 from krishna-254/feat/employee-milestone-indicators
feat: employee milestone indicators
2026-03-26 13:40:20 +05:30
Mihir Kandoi
afa66e4785 fix: keep from and to time blank until added explicitly (#53798) 2026-03-26 13:05:54 +05:30
Sudharsanan11
913168e8b6 fix(stock): update company validation for expense account in lcv 2026-03-26 12:56:59 +05:30
kavin-114
7e6bbcc3fb fix(stock): handle legacy single sle recon entries 2026-03-26 12:46:06 +05:30
Krishna Shirsath
9715637c80 Merge remote-tracking branch 'origin/develop' into feat/employee-milestone-indicators 2026-03-26 10:05:23 +05:30
Mihir Kandoi
3f74733942 fix: purchase invoice for internal transfers should not require PO (#53791) 2026-03-25 16:18:51 +00:00
diptanilsaha
e136bfbb61 fix(contract_template): restrict create, write and delete access only to System Manager (#53787) 2026-03-25 14:43:45 +00:00
MochaMind
00b780362b fix: sync translations from crowdin (#53475) 2026-03-25 11:47:01 +01:00
diptanilsaha
1d202fe739 Merge pull request #53779 from diptanilsaha/template_fixes 2026-03-25 14:56:36 +05:30
diptanilsaha
bc6561cdd0 fix(templates): using correct syntax of include in projects.html 2026-03-25 14:55:05 +05:30
diptanilsaha
d9760bbf4f fix(templates): escape attachment file_url and file_name in order.html and projects.html 2026-03-25 14:46:50 +05:30
Sudharsanan Ashok
a821c6669f fix(manufacturing): update condition for base hour rate calculation (#53753) 2026-03-25 11:37:38 +05:30
Pandiyan P
d43d308e2f fix(manufacturing): apply work order status filter in job card (#53766) 2026-03-25 11:20:04 +05:30
Mihir Kandoi
90cd957d6e fix: do not check for sub assembly reference for rm of fg (#53758) 2026-03-24 17:09:49 +00:00
iamkhanraheel
7b0bfe76cc fix: default permission for HR User role 2026-03-24 19:57:42 +05:30
ruthra kumar
14a46bf920 Merge pull request #53657 from ruthra-kumar/move_commits_inside_test_guard_clause
refactor(test): move remaining commits inside test guard
2026-03-24 17:43:17 +05:30
ruthra kumar
bc2b8da597 refactor(test): process statement of acc remove commit 2026-03-24 17:21:31 +05:30
ruthra kumar
fd2b76a4d2 refactor(test): move location creation to bootstrap in asset movement 2026-03-24 17:21:31 +05:30
ruthra kumar
8fd65d7afa refactor(test): make stock entry deterministic 2026-03-24 17:21:31 +05:30
ruthra kumar
2c53cf3902 refactor(test): make asset capitalization deterministic 2026-03-24 17:21:31 +05:30
ruthra kumar
d3cf8cb851 refactor(test): make ledger merge deterministic 2026-03-24 17:21:31 +05:30
ruthra kumar
77f41e120d refactor(test): SLA move company creation to bootstrap 2026-03-24 17:21:31 +05:30
ruthra kumar
426b7db3c8 refactor(test): move webform custom dt creation to boostrap 2026-03-24 17:21:31 +05:30
ruthra kumar
934740205a refactor(test): move custom doctype data setup to bootstrap 2026-03-24 17:21:31 +05:30
ruthra kumar
4454af8efd refactor(test): move tax category custom field creation to bootstrap 2026-03-24 17:21:31 +05:30
ruthra kumar
11fb00c21d refactor(test): move trial company creation to bootstrap 2026-03-24 17:21:31 +05:30
ruthra kumar
31ce09204f refactor(test): move purchase invoice dimension setup to bootstrap 2026-03-24 17:21:31 +05:30
Nishka Gosalia
6c354895d6 Merge pull request #53738 from nishkagosalia/item-form-cleanup 2026-03-24 16:28:10 +05:30
nishkagosalia
be55082751 refactor: item master ux improvements 2026-03-24 15:55:45 +05:30
ervishnucs
a518a735f3 fix: remove null from link_filters 2026-03-24 15:43:20 +05:30
Khushi Rawat
ec003342a0 Merge pull request #53588 from khushi8112/default-print-format-for-quotation
feat: default print format for Quotation
2026-03-24 15:36:34 +05:30
Shllokkk
3ba36212b0 refactor: clean and standardize print template for general ledger report 2026-03-24 15:32:13 +05:30
khushi8112
da41057cd6 fix: add quotation print format in the list 2026-03-24 13:03:24 +05:30
khushi8112
b9083411cc fix: remove unused print format 2026-03-24 12:17:42 +05:30
khushi8112
c99cec1071 fix: add closing div tab 2026-03-24 12:17:42 +05:30
khushi8112
4307cd5b1c feat: default print format for Quotation 2026-03-24 12:17:36 +05:30
ruthra kumar
9ed072ac83 refactor(test): move company setup to bootstrap 2026-03-24 11:57:45 +05:30
ruthra kumar
342ce65401 refactor(test): move dimension setup to test data bootstrap
and remove create_dimension() and disable_dimension()
2026-03-24 11:57:43 +05:30
ruthra kumar
ed76d6699a refactor(test): move commits inside test guard clause 2026-03-24 11:57:02 +05:30
ruthra kumar
cd5b0ea5fd Merge pull request #52802 from nabinhait/workspace-cleanup-1
fix: Removed quick access link from selling workspace
2026-03-24 11:30:53 +05:30
ruthra kumar
ae8068e833 Merge pull request #53302 from Shllokkk/xxs-xxe-fix
fix: sanitize genericode import inputs and secure XML parser
2026-03-24 11:27:15 +05:30
Nabin Hait
d7c48d645a fix: Removed quick access link from selling workspace 2026-03-24 11:11:56 +05:30
ruthra kumar
107fecd4b0 Merge pull request #53730 from khushi8112/asset-location-overwritten-by-accounting-dimension
fix: skip overwriting existing asset fields with accounting dimensions
2026-03-24 11:04:59 +05:30
khushi8112
2859a143f2 fix: skip overwriting existing asset fields with accounting dimensions 2026-03-24 02:19:44 +05:30
Khushi Rawat
bd9b0185f4 Merge pull request #53680 from khushi8112/better-opening-invoice-creation-tool
fix(UX): improve party selection UX with party name field
2026-03-24 01:35:54 +05:30
Khushi Rawat
0faa261729 Merge pull request #53646 from khushi8112/default-print-format-for-rfq
feat: default print format for Request for Quotation
2026-03-24 01:34:00 +05:30
Mihir Kandoi
91da450a31 chore: remove unused imports (#53722) 2026-03-23 16:39:55 +00:00
Pandiyan P
8ebc2e38ec fix(manufacturing): close work order status when stock reservation is… (#53714)
* fix(manufacturing): close work order status when stock reservation is enabled

* chore: better syntax

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-23 16:05:00 +00:00
diptanilsaha
96c79dfe8b chore(test_item_group): removed unused function _print_tree (#53716) 2026-03-23 15:50:01 +00:00
diptanilsaha
958bb6c619 chore: skip semgrep check for db.commit in BootStrapTestData (#53715) 2026-03-23 15:39:14 +00:00
Rohit Waghchaure
20787ef5da refactor: reposting for better peformance 2026-03-23 20:36:02 +05:30
Jatin3128
226aafa8cf feat: add setting to hide Subscription references across doctypes 2026-03-23 19:49:01 +05:30
ruthra kumar
30380851d8 Merge pull request #52285 from Jatin3128/payment_entry_ref
fix(Payment Entry): split orders as per the schedules in the reference table
2026-03-23 19:36:36 +05:30
Rucha Mahabal
248ea16d96 feat(employee): Create User button and form. (#52726)
* feat(employee): Create User button and form.

* feat(employee): Add automatic user creation feature and related validations. Create User on Import.

* refactor(employee): create user function -removed useless function calls

* refactor(employee): reorganize joining and employee exit tabs at the end.

* feat(employee): Add birthdays and work anniversaries indicator in form ,list view enhancements and new empty state.

* fix: add missing type hints to whitelisted function arguments

* fix(employee): add 'set_only_once' property to 'Create User Automatically' field

* refactor(employee): remove anniversary indicator logic from employee form

* fix: move Joining section before Exit, relabel Employee Exit -> Exit

* fix: reset employee listview empty state, add import btn instead

* fix: employee user creation

- consider prefered email as default in employee creation

- remove unused user parameter from `create_user` API

- remove unnecessary validations on user ID, already checked by user doctype hooks

- set company email only if empty

* fix: only validate auto user creation before insert

* fix: uncollapse User Details section in new form

* fix: hide Create User Automatically checkbox if user is already selected

* fix: set create user perm to 1 by default + persist option while saving employee

* fix: avoid setting unnecessary fields

* fix: fallback to Personal Email for user creation just like client-side

* fix: reset User ID and make it read-only if 'Create User Automatically' is set

* test: Create User Automatically

* test(fix): set company in employee

---------

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2026-03-23 17:47:02 +05:30
rohitwaghchaure
3f3ddf968e Merge pull request #53705 from rohitwaghchaure/fixed-support-63246
fix: batch validation for subcontracting receipt
2026-03-23 17:01:47 +05:30
Rucha Mahabal
a14f834589 test(fix): set company in employee 2026-03-23 16:52:11 +05:30
ervishnucs
03c9d16ca6 fix: fetch get_item_tax_template while update items 2026-03-23 16:45:20 +05:30
Rohit Waghchaure
b8d201658a fix: batch validation for subcontracting receipt 2026-03-23 16:29:47 +05:30
Rucha Mahabal
cc93b14154 Merge branch 'develop' into feat/employee-creation-and-lifecycle 2026-03-23 16:11:10 +05:30
Rucha Mahabal
d4ecede3c3 test: Create User Automatically 2026-03-23 16:09:58 +05:30
Asief Tejani
99d55ab8d8 docs(README): sync erpnext docker with frappe_docker repo (#53447)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-03-23 16:00:03 +05:30
diptanilsaha
8ea0cc90df fix: check for submit permissions instead of write permissions when updating status (#53697) 2026-03-23 15:43:33 +05:30
Rucha Mahabal
2be6bb694f fix: reset User ID and make it read-only if 'Create User Automatically' is set 2026-03-23 15:42:13 +05:30
Rucha Mahabal
31af13a5e6 fix: fallback to Personal Email for user creation just like client-side 2026-03-23 15:31:51 +05:30
Sudharsanan Ashok
41f986ff83 fix(manufacturing): update non-stock item dict (#53689) 2026-03-23 15:16:37 +05:30
khushi8112
469bb0ba4e fix: party name not updating correctly 2026-03-23 09:44:41 +00:00
khushi8112
8fd9b88cd9 fix(UX): improve party selection UX with party name field 2026-03-23 09:44:41 +00:00
Rucha Mahabal
97bb100010 fix: avoid setting unnecessary fields 2026-03-23 15:06:15 +05:30
khushi8112
6b9fb77772 fix: set default print format for when downlod pdf 2026-03-23 15:04:14 +05:30
khushi8112
2af0d9cf6c feat: default print format for Request for Quotation 2026-03-23 15:04:14 +05:30
Nishka Gosalia
75ce885684 Merge pull request #53695 from nishkagosalia/fix-type-hint-stock 2026-03-23 15:01:43 +05:30
Krishna Shirsath
3e56b8d71d fix(employee): removed milestone lable and remove unnecessary margins 2026-03-23 14:47:54 +05:30
nishkagosalia
2f72ae9afd fix: type hint in stock module 2026-03-23 14:37:46 +05:30
Nishka Gosalia
ca877ba223 Merge pull request #53649 from nishkagosalia/st-63180 2026-03-23 14:27:13 +05:30
diptanilsaha
a908bc7e92 fix(trends): added validation for period_based_on filter (#53690) 2026-03-23 14:23:42 +05:30
Rucha Mahabal
091899d0df fix: set create user perm to 1 by default + persist option while saving employee 2026-03-23 14:23:01 +05:30
Rucha Mahabal
ec3302d1c1 fix: hide Create User Automatically checkbox if user is already selected 2026-03-23 14:06:56 +05:30
Rucha Mahabal
1466df91bd fix: uncollapse User Details section in new form 2026-03-23 14:06:24 +05:30
Rucha Mahabal
ee1aa10328 fix: only validate auto user creation before insert 2026-03-23 13:37:07 +05:30
nishkagosalia
dcd0509089 chore: Adding new argument in status updater to skip qty validation 2026-03-23 13:18:26 +05:30
Rucha Mahabal
613d36a139 fix: employee user creation
- consider prefered email as default in employee creation

- remove unused user parameter from `create_user` API

- remove unnecessary validations on user ID, already checked by user doctype hooks

- set company email only if empty
2026-03-23 12:52:23 +05:30
Mihir Kandoi
7a61d6fcd5 fix: shipping rule applied twice on non stock items (#53655) 2026-03-23 06:49:39 +00:00
Mihir Kandoi
5154102468 fix: PO should not be required for internal transfers (#53681) 2026-03-23 06:32:26 +00:00
Rucha Mahabal
d99d16423a fix: reset employee listview empty state, add import btn instead 2026-03-23 11:59:05 +05:30
Rucha Mahabal
000b5b72d5 fix: move Joining section before Exit, relabel Employee Exit -> Exit 2026-03-23 11:27:00 +05:30
MochaMind
f483d9ff13 chore: update POT file (#53677) 2026-03-22 14:47:24 +01:00
rohitwaghchaure
c1c3757943 Merge pull request #53673 from rohitwaghchaure/fixed-stock-queue-not-updated
fix: stock queue for SABB
2026-03-22 13:00:28 +05:30
Rohit Waghchaure
3fcf308ed8 fix: stock queue for SABB 2026-03-22 12:06:43 +05:30
rohitwaghchaure
145a42eb30 Merge pull request #53638 from rohitwaghchaure/fixed-deadlock-issue-for-sle
fix: deadlock issue for SLE
2026-03-21 14:03:40 +05:30
Mihir Kandoi
fa35fbdb8e fix: do not overwrite expense account in stock entry (#53658) 2026-03-20 08:02:32 +00:00
diptanilsaha
8e17c722fb fix: validate permission before updating status (#53651) 2026-03-19 14:49:29 +00:00
Nishka Gosalia
ffd3e90806 Merge pull request #53645 from nishkagosalia/gh-53526 2026-03-19 18:29:00 +05:30
nishkagosalia
7f70e62c30 fix: Adding validation for operation time in BOM 2026-03-19 17:31:31 +05:30
ruthra kumar
44247f63d5 Merge pull request #53594 from ruthra-kumar/fix_failing_patch
fix: patch failure due to dependency
2026-03-19 17:30:35 +05:30
ruthra kumar
bb3cee8ef5 fix: patch failure due to dependency 2026-03-19 16:13:20 +05:30
Mihir Kandoi
fc25d83a9e fix: co by product patch for v14 migration (#53644) 2026-03-19 10:31:46 +00:00
Rohit Waghchaure
f48b03c6ec fix: deadlock issue for SLE 2026-03-19 14:31:17 +05:30
Sudharsanan Ashok
442fe9a833 fix(stock): fix email error message (#53606) 2026-03-19 07:11:41 +00:00
diptanilsaha
086fea7cf0 fix(payment_schedule): using show_alert instead of msgprint for non-selection of payment schedule (#53623) 2026-03-19 12:29:28 +05:30
Sakthivel Murugan S
256d267a3b fix: set customer details on customer creation at login (#53509) 2026-03-19 12:04:31 +05:30
Pandiyan P
0fdc1bc497 fix(stock): handle NoneType error (#53593) 2026-03-19 11:56:58 +05:30
Mihir Kandoi
71293bcf73 refactor: remove test file import in stock ageing report (#53619) 2026-03-19 11:52:57 +05:30
Mihir Kandoi
f1e36b09f9 fix: consider returned qty in subcontracting report (#53616) 2026-03-19 05:24:58 +00:00
Mihir Kandoi
743970a8c6 fix: python error in manufacture entry if transfer against is job card (#53615) 2026-03-19 04:57:37 +00:00
Sowmya
03d8a7a6af fix: ignore cost center (#53063) 2026-03-19 09:54:17 +05:30
Vishnu Priya Baskaran
31b44534df fix: check posting_date in args (#53303) 2026-03-19 09:52:37 +05:30
ruthra kumar
df7e6b7a79 Merge pull request #47910 from ruthra-kumar/ci_lightmode_runner
refactor(test): repo wide test suite refactor to achieve deterministic behaviour
2026-03-18 21:42:16 +05:30
ruthra kumar
eb143ba742 refactor(test): cleanup; remove redundant attribute 2026-03-18 20:59:39 +05:30
ruthra kumar
22c05d4b8f refactor(test): make pick list deterministic 2026-03-18 20:59:39 +05:30
ruthra kumar
7e8f9f10f3 refactor(test): make item wise sales register deterministic 2026-03-18 20:59:39 +05:30
ruthra kumar
5263386bb2 refactor(test): replace integration test case with ERPNextTestSuite 2026-03-18 20:59:39 +05:30
ruthra kumar
72d08902fd refactor(test): make purchase order deterministic 2026-03-18 20:59:39 +05:30
ruthra kumar
23d35c6cca refactor(test): make sales invoice deterministic 2026-03-18 20:59:39 +05:30
ruthra kumar
da8fcde4a8 refactor(test): make location determinisitic 2026-03-18 20:59:38 +05:30
ruthra kumar
6eea0a2299 refactor(test): make bank clearance deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
9e63c14cef refactor(test): make sales invoice deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
21b25ce96c refactor(test): hardcoded names over dynamic 2026-03-18 20:59:38 +05:30
ruthra kumar
7cad642a11 refactor(test): use JSON for company master 2026-03-18 20:59:38 +05:30
ruthra kumar
5237e58f29 refactor(test): remove explicit call to master data setup 2026-03-18 20:59:38 +05:30
ruthra kumar
e67165d6ce refactor(test): hardcoded names over dynamic ones
Much faster bootstrap without those get_doc calls
2026-03-18 20:59:38 +05:30
ruthra kumar
5a4a77f5d2 refactor: move test bootstrap to module 2026-03-18 20:59:38 +05:30
ruthra kumar
96b82624cd refactor(test): speed up setup 2026-03-18 20:59:38 +05:30
ruthra kumar
fc8fadf455 refactor(test): make stock entry deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
518800cd2f refactor(test): remove redundant before_tests 2026-03-18 20:59:38 +05:30
ruthra kumar
0f2f53cbd0 refactor(test): make process deferred accounting deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
19e03ccdde refactor(test): make bom deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
7fea5a5ca2 refactor(test): make customer deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
cb693e05bf refactor(test): tax rule; removed setUpClass, tearDownClass 2026-03-18 20:59:38 +05:30
ruthra kumar
aa998219b1 refactor(test): common make function 2026-03-18 20:59:38 +05:30
ruthra kumar
4d65cb907f refactor(test): make bom stock calculated deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
c2dea5245d refactor(test): make pos profile deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
07374b5dbc refactor(test): make pos opening entry deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
25279321ac refactor(test): make pos invoice merge log deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
0474d0c4e8 refactor(test): make pos closing deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
ec9b2f0567 refactor(test): make stock test_utils deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
6c27efeeea refactor(test): make currency exchange deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
ffca80daa5 refactor(test): make sales partner target variance deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
2c0466d637 refactor(test): make sales order analysis deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
a91ed14aec refactor(test): make uae vat audit deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
96f9fc3484 refactor(test): make uae vat 201 deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
e01e3c0a62 refactor(test): make job card deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
4777b060ba refactor(test): make opportunity summary by sales stage deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
ed099bcd85 refactor(test): make queries deterministic 2026-03-18 20:59:38 +05:30
ruthra kumar
d81fd25325 refactor(test): make item wise details deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
2eec0f704c refactor(test): make accounts controller deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
b4245e9353 refactor(test): make subcontracting controller deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
323a3dd573 refactor(test): make test mapper deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
56a5ddae8f refactor(test): make item wise inventory account deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
a3d8bb8d21 refactor(test): make distributed discount deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
18fe191929 refactor(test): make requested items order and receive deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
f4d355a0e4 refactor(test): make accounts/test/test_utils.py deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
0ef004ce48 refactor(test): make gross profit deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
47cae808c5 refactor(test): make sales payment summary deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
1689d6a9fc refactor(test): make consolidated trial balance report deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
12ae84401a refactor(test): remove redundant tearDown, tearDownClass and rollback 2026-03-18 20:59:37 +05:30
ruthra kumar
8090caa026 refactor(test): make inventory dimension deterministic
fixed flaky test 'test_inventory_dimension'

use '3' precision for test_opening_balnace
2026-03-18 20:59:37 +05:30
ruthra kumar
ff87eedd96 refactor(test): make item group deterministic
'Item Group C' follows 'Item Group B'. So `lft` will increase while
`rgt` stays the same.
2026-03-18 20:59:37 +05:30
ruthra kumar
57f94b3ac2 refactor(test): make packed item deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
3b65364828 chore: typo 2026-03-18 20:59:37 +05:30
ruthra kumar
b5db1e9e1f refactor(test): remove redundant create_asset_category 2026-03-18 20:59:37 +05:30
ruthra kumar
79c23fc6c6 refactor(test): make sales invoice deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
31718d2066 refactor(test): make item group deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
cd499d4955 refactor(test): make timesheet deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
4484863baa refactor(test): make task deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
ddcd8a03ee refactor(test): make project deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
ce50e23536 refactor(test): make maintenance schedule deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
3c904cbc5f refactor(test): make leads deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
06ddc80292 refactor(test): make purchase order deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
7da61621cc refactor(test): make asset repair deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
075fe3f668 refactor(test): make asset deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
b2242d3cce refactor(test): make loyalty point entry deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
c55628a55d refactor(test): make dunning deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
3a5869e525 refactor(test): make budget deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
bb51e3147c refactor(test): make auto match party deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
5415e2ca82 refactor(test): make bank clearance deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
e6885af96b refactor(test): remove explicit commit and dead code 2026-03-18 20:59:37 +05:30
ruthra kumar
6d86bfe2e3 refactor(test): rollback on tearDown 2026-03-18 20:59:37 +05:30
ruthra kumar
70059d1ec0 refactor(test): make irs supplier test deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
d2da518e02 refactor(test): make party specific item deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
d119e3a8e8 refactor(test): make subcontracting receipt deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
577be4f9ad refactor(test): make purchase receipt deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
8dd9ab6475 refactor(test): make stock reconciliation deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
ee8e96dbcf refactor(test): make subcontracting order deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
faaad3ae8e refactor(test): make purchase receipt deterministic 2026-03-18 20:59:37 +05:30
ruthra kumar
c5f2ef454f refactor(test): make material request deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
7224dcce26 refactor(test): make stock ledger entry deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
f758ee4adc refactor(test): make item deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
26d7900590 refactor(test): make work order deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
2d1db2e403 refactor(test): make purchase receipt deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
4111d4ee1d refactor: fix logical bug and make stock settings test deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
d8be59b1ba refactor(test): make stock entry deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
c38696157c refactor(test): make serial no deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
dab31bc36d refactor(test): make serial and batch bundle deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
d790a1d3a6 refactor(test): make pick list deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
298960bfc6 refactor(test): make packed item deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
b8437f7f22 refactor(test): make material request deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
db49e6d830 refactor(test): make LCV deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
7853b779bd refactor(test): make item price deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
23cdd82de1 refactor(test): make delivery trip deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
5c85074f54 refactor(test): make delivery note deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
8d20da91d1 refactor(test): make transaction deletion record deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
0b046647e7 refactor(test): make item group deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
9231dbbb2f refactor(test): make employee deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
0dbd10893e refactor(test): make department deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
f2a85fd134 refactor(test): make currency exchange deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
7a8ece76ea refactor(test): make test deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
8b720ffd4f refactor(test): make project deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
6e3ea35dda refactor(test): make sales order deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
a4f8920d97 refactor(test): make quotation deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
7b8e15c5b8 refactor(test): make production plan deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
464c09ed10 refactor(test): make job card tests deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
bfb8837c54 refactor(test): make bom tests deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
7b26a4c2eb refactor(test): make bom update tool tests deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
8c5276c5e1 refactor(test): make plaid settings deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
2516cdafcb refactor(test): make financial reports deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
fcee9ad778 refactor(test): make sales invoice deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
5b0c41a3f7 refactor(test): make pos invoice deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
f5e69f2602 refactor(test): better setup for loyalty point and POS Invoice 2026-03-18 20:59:36 +05:30
ruthra kumar
d69d2c374f refactor(test): pos invoice tests are almost deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
524118e108 refactor(test): make timesheet and activity type deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
b64231ce2e refactor(test): make maintenance schedule deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
1a6358ec70 refactor(test): make prospect deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
0b2dbcf30c refactor(test): make opportunity deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
3b5667c007 refactor(test): remove empty IntegrationTestCase 2026-03-18 20:59:36 +05:30
ruthra kumar
e490d5044b refactor(test): make tax withholding tests deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
87f0247f2b refactor(test): ensure new bankers rounding method is set 2026-03-18 20:59:36 +05:30
ruthra kumar
91875fdf8d refactor(test): set precision in class setup 2026-03-18 20:59:36 +05:30
ruthra kumar
3dc3b2b64e refactor(test): make sales invoice tests deterministic
- allow_negative_stock is required for test_taxes_merging_from_delivery_note
2026-03-18 20:59:36 +05:30
ruthra kumar
eb70060798 refactor(test): make purchase invoice tests deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
2edbbb6052 refactor: avoid name clash 2026-03-18 20:59:36 +05:30
ruthra kumar
c2e9a91a94 refactor(test): handle setup of Workstation Operation Component 2026-03-18 20:59:36 +05:30
ruthra kumar
81ef8655c2 refactor(tests): make asset maintenance tests deterministic 2026-03-18 20:59:36 +05:30
ruthra kumar
def64ae864 refactor(test): incorrect fieldname for abbrevation 2026-03-18 20:59:36 +05:30
ruthra kumar
379f12daee refactor(test): make asset repair tests deterministic 2026-03-18 20:59:35 +05:30
ruthra kumar
cee55a1518 refactor(test): make supplier quotation deterministic 2026-03-18 20:59:35 +05:30
ruthra kumar
1560619fe0 refactor(test): install supplier scorecard presets 2026-03-18 20:59:35 +05:30
ruthra kumar
5b20935235 chore: remove default templated test case 2026-03-18 20:59:35 +05:30
ruthra kumar
4eb23df627 refactor(test): set_user utility method 2026-03-18 20:59:35 +05:30
ruthra kumar
253fbc2a70 refactor: replace all IntegrationTestCase -> ERPNextTestSuite 2026-03-18 20:59:35 +05:30
ruthra kumar
ea2763432d refactor: utility to return default supplier scorecards 2026-03-18 20:59:35 +05:30
ruthra kumar
03a64b67d9 refactor(test): flaky test in lead 2026-03-18 20:59:35 +05:30
ruthra kumar
2309bea1a0 refactor(test): flaky tax rule testsuite
- Make Sales Stage preset
2026-03-18 20:59:35 +05:30
ruthra kumar
3c14621ae0 refactor(test): load shipping rule records 2026-03-18 20:59:35 +05:30
ruthra kumar
27474eceee refactor(test): fix flaky shareholder test 2026-03-18 20:59:35 +05:30
ruthra kumar
36a9893558 refactor(test): fix flaky process deferred accounting tests 2026-03-18 20:59:35 +05:30
ruthra kumar
d37f2ada65 refactor(test): flaky pricing rule tests 2026-03-18 20:59:35 +05:30
ruthra kumar
c0647cf93b refactor(test): flaky stock entry; load records json 2026-03-18 20:59:35 +05:30
ruthra kumar
95d6bbe7ad refactor(test): flaky pos invoice merge log test 2026-03-18 20:59:35 +05:30
ruthra kumar
aeae6d4a10 refactor(test): flaky post invoice test 2026-03-18 20:59:35 +05:30
ruthra kumar
0914fd695d refactor(test): flaky pos invoice test; load stock entry json 2026-03-18 20:59:35 +05:30
ruthra kumar
a5a6a3bc9c refactor(test): flaky PE test; load currency exchange record 2026-03-18 20:59:35 +05:30
ruthra kumar
56542c805a refactor: load journal entry test records and make holiday list 2026-03-18 20:59:35 +05:30
ruthra kumar
6041574209 refactor: utility to load JSON records 2026-03-18 20:59:35 +05:30
ruthra kumar
0b14d1fe34 refactor(test): update system settings in super() 2026-03-18 20:59:35 +05:30
ruthra kumar
6572dc286a refactor(test): flaky test setup in pos closing entry 2026-03-18 20:59:35 +05:30
ruthra kumar
223737cbe2 refactor(test): fix flaky test setup for opening invoice creation 2026-03-18 20:59:35 +05:30
ruthra kumar
b00df01817 refactor(test): flaky test setup in Exchange Rate Revaluation 2026-03-18 20:59:35 +05:30
ruthra kumar
ce8ce10ef1 refactor(test): flaky test data setup for coupon code 2026-03-18 20:59:35 +05:30
ruthra kumar
97b922f9f1 refactor(test): make price list - more test records 2026-03-18 20:59:35 +05:30
ruthra kumar
92f0175e91 refactor(test): flaky test data in bank reconciliation tool 2026-03-18 20:59:35 +05:30
ruthra kumar
66c7815369 refactor(test): call super() method first 2026-03-18 20:59:35 +05:30
ruthra kumar
f6b6c93c10 refactor(test): make customer 2026-03-18 20:59:35 +05:30
ruthra kumar
8393c1b4f6 refactor(test): make price list and update selling settings 2026-03-18 20:59:35 +05:30
ruthra kumar
20b835a53b refactor(test): make item attribute 2026-03-18 20:59:35 +05:30
ruthra kumar
1fb26b5989 refactor(test): make test accounts 2026-03-18 20:59:35 +05:30
ruthra kumar
59144e03bc refactor(test): make item tax template 2026-03-18 20:59:35 +05:30
ruthra kumar
0feedd183b refactor(test): make uom 2026-03-18 20:59:35 +05:30
ruthra kumar
402c7df643 refactor(test): make item group 2026-03-18 20:59:35 +05:30
ruthra kumar
526fc1778f refactor(test): make item 2026-03-18 20:59:35 +05:30
ruthra kumar
aa76fc5d7c refactor(test): make warehouse 2026-03-18 20:59:35 +05:30
ruthra kumar
a769c71642 chore: typo in setup method 2026-03-18 20:59:35 +05:30
ruthra kumar
3ea1283613 refactor(test): make persistent location 2026-03-18 20:59:35 +05:30
ruthra kumar
6fc0a53bae refactor(test): setup fiscal years without any gap 2026-03-18 20:59:35 +05:30
ruthra kumar
c6b661526d refactor(test): make cost center 2026-03-18 20:59:35 +05:30
ruthra kumar
0c8145e924 refactor: suppress welcome mail 2026-03-18 20:59:35 +05:30
ruthra kumar
033e826242 refactor(test): even more master data setup 2026-03-18 20:59:35 +05:30
ruthra kumar
783d51e8cc refactor(test): make all presets in setupclass 2026-03-18 20:59:35 +05:30
ruthra kumar
aff6452075 refactor: temporary standing decorator for change_settings 2026-03-18 20:59:35 +05:30
ruthra kumar
33f4791698 refactor: replace IntegrationTestCase with ERPNextTestCase repo-wide
- import ERPNextTestSuite
 - use it on test class
2026-03-18 20:59:35 +05:30
ruthra kumar
4027b82714 refactor(test): bare bones presets for company 2026-03-18 20:59:34 +05:30
ruthra kumar
5c112daa1e refactor(test): make persistent company records
'test_coa_based_on_country_template' made deterministic
2026-03-18 20:59:34 +05:30
ruthra kumar
a00814d849 refactor(test): IntegraionTestCase -> python's built-in TestCase 2026-03-18 20:59:34 +05:30
ruthra kumar
3cee89d827 chore: drop dead hook 2026-03-18 20:59:34 +05:30
ruthra kumar
2447042060 chore: remove global dependencies 2026-03-18 20:59:34 +05:30
ruthra kumar
319e220efe chore: remove IGNORE_TEST_RECORD_DEPENDENCIES 2026-03-18 20:59:34 +05:30
ruthra kumar
8eef42d075 chore: remove EXTRA_TEST_RECORD_DEPENDENCIES 2026-03-18 20:59:34 +05:30
ruthra kumar
002b4fb048 chore: delete all test_records.json 2026-03-18 20:59:34 +05:30
ruthra kumar
4167609e41 ci: run parallel test in lightmode
- modify individual test CI. let it be dormat for now.

Acked-by: ruthra kumar <ruthra@erpnext.com>
2026-03-18 20:59:34 +05:30
Mihir Kandoi
6cb6a52ded fix: incorrect sle calculation when doc has project (#53599) 2026-03-18 13:19:30 +00:00
rohitwaghchaure
0d8f6b05e3 Merge pull request #53246 from aerele/stock-entry-cost-center
feat: add cost center field to the stock entry accounting dimension tab
2026-03-18 11:49:01 +05:30
Lakshit Jain
072ec9b7ae fix: initialize all tax columns to resolve Key error in item_wise_sales_register and item_wise_purchase_register reports (#53323) 2026-03-17 23:37:06 +05:30
Mihir Kandoi
aba1f34de0 Modify CODEOWNERS to include additional owners (#53575) 2026-03-17 17:07:48 +00:00
Sudharsanan Ashok
31d14df37b fix(stock): add company filter while fetching batches (#53369) 2026-03-17 16:39:21 +00:00
Mihir Kandoi
b433852f8a chore: make supplier data expanded by default in PI (#53565) 2026-03-17 22:08:17 +05:30
Sudharsanan Ashok
fe5f16cb18 fix(stock): fix the property setter (#53422) 2026-03-17 22:02:41 +05:30
mergify[bot]
fe85dc10cc fix(italy): fix e-invoice ScontoMaggiorazione structure and included_in_print_rate support (backport #53334) (#53568)
Co-authored-by: Arturo <tamburro92@users.noreply.github.com>
2026-03-17 22:01:15 +05:30
Khushi Rawat
445aef7d17 Merge pull request #53446 from Kesavan-code/Fix-53445
fix: fetch accounting dimensions from child row in asset creation
2026-03-17 21:37:34 +05:30
Mihir Kandoi
f319857939 chore: add documentation link in valuation method field (#53564) 2026-03-17 15:32:29 +00:00
Sudharsanan Ashok
517310182e fix(manufacturing): update working hours validation (#53559) 2026-03-17 20:47:46 +05:30
diptanilsaha
3ff2871f24 fix(sales_invoice): reset payment methods on pos_profile change (#53514) 2026-03-17 19:29:30 +05:30
Soham Kulkarni
72835f9a58 Merge pull request #53322 from sokumon/add-clear-demo-data 2026-03-17 17:05:51 +05:30
ruthra kumar
dd4b83906d Merge pull request #53535 from aerele/remove-payables-receivables-workspace-v16
fix: remove payables and receivables workspace
2026-03-17 17:03:02 +05:30
ruthra kumar
41fbb916a0 fix: incorrect user perms in queries (#53548)
* chore: remove incorrect import

* fix: use qb to prevent incorrect sql due to user permissions
2026-03-17 16:32:05 +05:30
ruthra kumar
04b967bd6d fix: use qb to prevent incorrect sql due to user permissions 2026-03-17 16:06:54 +05:30
sokumon
ed3444de5a fix: use same label 2026-03-17 15:48:24 +05:30
Mihir Kandoi
ef09cffa58 chore: initiate release twice in a week (#53543) 2026-03-17 10:17:02 +00:00
Nishka Gosalia
49581e7408 fix: Creating new item price incase of changes in expired item price (#53534)
Co-authored-by: Nishka Gosalia <nishkagosalia@Nishkas-MacBook-Air.local>
2026-03-17 15:32:17 +05:30
ruthra kumar
fc2edfbded chore: remove incorrect import 2026-03-17 15:23:05 +05:30
rohitwaghchaure
13765e7557 Merge pull request #53500 from rohitwaghchaure/fixed-valuation-for-non-batchwise-valuation-batches
fix: valuation rate for no Use Batch wise Valuation batches
2026-03-17 14:12:15 +05:30
Rohit Waghchaure
4befa15198 fix: valuation rate for no Use Batch wise Valuation batches 2026-03-17 13:17:00 +05:30
ravibharathi656
26a9646407 fix: remove payables and receivables workspace 2026-03-17 12:36:57 +05:30
Jatin3128
a9e52833fe fix(Payment Entry): split orders as per the schedules in the refrence table 2026-03-17 11:28:48 +05:30
Abdus Samad
65ed936ff3 fix: add item_name to quick entry fields in Item doctype (#53530) 2026-03-17 10:56:30 +05:30
Nikhil Kothari
ef32622166 fix(banking): include paid purchase invoices in reports and bank clearance (#52675)
* fix(banking): include paid purchase invoices in reports and bank clearance

* fix: condition for amounts not reflected in system

* fix: set Sales Invoice to be the payment document in bank rec

* fix: add additional filter for `is_paid`

* fix: added is_paid

* fix: added invoice number in bank clearance tool

* chore: make requested changes

* fix: exclude opening JEs

* fix: bring back banking icon in desktop
2026-03-17 10:17:32 +05:30
Jeraldin P J
e2667ab098 fix: enable logs to track changes in doctype. (#53491)
Co-authored-by: jeraldin2003 <jeraldin2003>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-16 18:21:26 +00:00
Sanjesh-Raju
0a38389bc3 fix: correct overlap detection in JobCard.has_overlap (#53473)
Co-authored-by: Sanjesh <rsanjesh64@gmail.com>
Co-authored-by: Tridots Tech <info@tridotstech.com>
2026-03-16 18:18:30 +00:00
Nishka Gosalia
953f089c06 feat: Adding requested qty in packed item (#53486)
* feat: Adding requested qty in packed item

* fix: correct import path

---------

Co-authored-by: Nishka Gosalia <nishkagosalia@Nishkas-MacBook-Air.local>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-03-16 18:09:39 +00:00
rohitwaghchaure
87785a2886 Merge pull request #53513 from rohitwaghchaure/fixed-do-not-set-rate-for-si
fix: do not set valuation rate for invoice without update stock
2026-03-16 23:32:52 +05:30
Abdus Samad
4cd150ba7a fix: change "Date" label to "Posting Date" in Sales Invoice and Purchase Invoice (#53503) 2026-03-16 17:57:44 +00:00
Rohit Waghchaure
bec9e48435 fix: do not set valuation rate for invoice without update stock 2026-03-16 23:08:55 +05:30
Shllokkk
0ef7594536 Merge pull request #53223 from Shllokkk/dashboard-chart-fix
Bank balance chart fix for payments dashboard
2026-03-16 20:01:39 +05:30
diptanilsaha
09dd2f851d fix(support-settings): disable the auto-close tickets feature if close_issue_after_days is set to 0 (#53499) 2026-03-16 11:47:01 +00:00
rohitwaghchaure
b5d24f5971 Merge pull request #53495 from rohitwaghchaure/fixed-support-62674
fix: stock adjustment entry
2026-03-16 15:17:12 +05:30
Rohit Waghchaure
af3067ee23 fix: stock adjustment entry 2026-03-16 14:51:09 +05:30
diptanilsaha
5f2c24a199 fix(p&l_statement): disable accumulated value filter by default (#53488) 2026-03-16 07:18:06 +00:00
Mihir Kandoi
fd7d5bdae1 fix: use bom source warehouse in WO created from PP (#53478) 2026-03-16 10:46:28 +05:30
Mihir Kandoi
ebb4a3d053 fix: use bom source warehouse in WO created from PP 2026-03-16 10:29:31 +05:30
Jeraldin P J
c09ea94133 fix: remove supplier selection dialog when creating Purchase Order from Material Request (#53391)
Co-authored-by: jeraldin2003 <jeraldin2003>
Co-authored-by: Nikhil Kothari <nik.kothari22@live.com>
2026-03-16 09:45:30 +05:30
Raffael Meyer
9e8c70e6b4 chore: add docs to project URLs (#53467) 2026-03-15 13:36:00 +00:00
MochaMind
008b296014 chore: update POT file (#53464) 2026-03-15 14:31:26 +01:00
sokumon
413b119ec6 fix: add icon in clear demo data 2026-03-15 17:49:32 +05:30
diptanilsaha
7a8d1931ed fix(serial_and_batch_bundle_selector): handle CSV attachment properly (#53460) 2026-03-15 12:58:58 +05:30
Mihir Kandoi
5ba6446451 fix: sales order indicator should be based on available qty rather th… (#53456) 2026-03-15 09:43:11 +05:30
Mihir Kandoi
708e4fa2be fix: sales order indicator should be based on available qty rather than delivered qty 2026-03-15 09:41:32 +05:30
[Kesavan-001]
e0fb31f81e Fix:Cost center mapping issue 2026-03-14 13:26:46 +05:30
[Kesavan-001]
a084feba96 Fix:Cost center mapping issue 2026-03-14 12:50:47 +05:30
[Kesavan-001]
10fe8580d5 Fix:Cost center mapping issue 2026-03-14 12:39:48 +05:30
David
b5a21855f6 feat(stock): implement fallback logic for Delivery Trip address mapping (#53260) 2026-03-14 05:41:19 +00:00
diptanilsaha
c99598e22e Merge pull request #53309 from frappe/l10n_develop 2026-03-14 02:39:06 +05:30
Sagar Vora
be85ecba1b Merge pull request #53430 from sagarvora/auto-cancel-exempt
fix: exempt ledger entries and closing balance from auto-cancellation
2026-03-13 18:06:21 +00:00
Sagar Vora
c069a1787d fix: hide cancel button from ledger / closing balance doctypes 2026-03-13 23:20:42 +05:30
MochaMind
67a80127e3 fix: Portuguese, Brazilian translations 2026-03-13 23:07:09 +05:30
Sagar Vora
56efe5e82c fix: exempt ledger entries and account closing balance from auto-cancellation 2026-03-13 23:03:13 +05:30
Shllokkk
8e5692d8a3 feat(report): add service start/end date and amount with roll-ups in deferred revenue/expense report 2026-03-13 21:31:37 +05:30
ruthra kumar
b1c1bca2b8 Merge pull request #53423 from ruthra-kumar/remove_fw_added_total_rows_from_trends_reports
refactor: disable total row in trends report
2026-03-13 18:02:05 +05:30
ruthra kumar
4dbc72b301 refactor: disable total row in trends report 2026-03-13 17:37:04 +05:30
ruthra kumar
b9a47d85db Merge pull request #53415 from ruthra-kumar/broken_cost_center_filter_in_get_outstanding_documents
fix: broke cost center filter in get outstanding reference docs
2026-03-13 15:24:28 +05:30
ruthra kumar
7dfe36fdce fix: broke cost center filter in get outstanding reference docs 2026-03-13 14:55:56 +05:30
Khushi Rawat
e85eb90ec7 Merge pull request #53416 from khushi8112/subsidiary-companies-value-in-purchase-analytics
feat: show subsidiary companies value in purchase analytics
2026-03-13 14:42:06 +05:30
khushi8112
9f755ad65a feat: show subsidiary companies value in purchase analytics 2026-03-13 14:37:11 +05:30
Mihir Kandoi
bd87a7e612 Revert "fix(regional): rename duplicate Customer fields in Italy setup" (#53409) 2026-03-13 07:27:32 +00:00
ljain112
e68f149d3a fix: correct item valuation when "Deduct" is used in Purchase Invoice and Receipt. 2026-03-13 11:53:08 +05:30
Smit Vora
20fc9c7b18 Merge pull request #53396 from vorasmit/same-phantom-different-fg 2026-03-13 09:44:42 +05:30
Khushi Rawat
f08293efec Merge pull request #53390 from khushi8112/asset-purchase-amount-currency
refactor: show company currency in purchase amount label
2026-03-13 00:47:37 +05:30
khushi8112
6219a9e6f0 fix: update label on company change 2026-03-13 00:27:43 +05:30
khushi8112
b4c82c0f1a refactor: show company currency in purchase amount label 2026-03-13 00:27:43 +05:30
MochaMind
7362f2e5fb fix: Portuguese, Brazilian translations 2026-03-12 23:05:16 +05:30
MochaMind
62715787c1 fix: Swedish translations 2026-03-12 23:05:07 +05:30
Smit Vora
b1e1c65774 chore: phantom qty unused in sub_assembly_items 2026-03-12 21:16:03 +05:30
Smit Vora
1975ae4486 test: ensure phantom BOM explosion across all items 2026-03-12 20:31:56 +05:30
Solede
c6efc403cd fix(regional): rename duplicate Customer fields in Italy setup (#50921)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:25:35 +05:30
Smit Vora
e57de4311c fix: correctly group RMs of same phantom from different FG 2026-03-12 20:21:18 +05:30
Mihir Kandoi
999a72a16a fix: get_item_tax_info type hints (#53392) 2026-03-12 14:07:49 +00:00
Sagar Vora
d8a56e9943 Merge pull request #53395 from shubhdoshi21/flag-fix
fix: update invalid syntax for flags
2026-03-12 12:39:22 +00:00
Shubh Doshi
c6e6859090 fix: update invalid syntax for flags 2026-03-12 17:52:10 +05:30
Shubh Doshi
ad8c05426e fix: make ledger entries submittable and cleanup invalid test submissions (#52921)
* fix: enable submittability for ledger entries and cleanup invalid test submissions

* fix: reverted child table submittability

* fix: added ignore_links flag for back gl entry

* fix: add ignore_links for reconcile,cancelled PLE,cancelled SLE

* fix: update test_recreate_stock_ledgers to use db.delete instead of doc.delete

* chore: temporary test against frappe PR 37009

* fix: make Advance Payment Ledger Entry submittable

* refactor: add extra line for create_shipping_rule

* chore: revert temporary test against frappe PR 37009

* fix: use parent doc save with ignore_validate_update_after_submit for child table updates

* chore: temporary test against frappe PR 37009

* chore: revert temporary test against frappe PR 37009

* fix: use skip_docstatus_validation
2026-03-12 16:27:51 +05:30
Khushi Rawat
0e888cc86b Merge pull request #53379 from khushi8112/correct-filter-for-asset-in-purchase-receipt
fix: use correct filter to get the composite assets
2026-03-12 15:10:34 +05:30
khushi8112
f5a3227349 fix: use correct filter to get the composite assets 2026-03-12 14:51:25 +05:30
Mihir Kandoi
7f644785ce fix: do not modify rate in the child item merely for comparison (#53301) 2026-03-12 14:30:00 +05:30
Mihir Kandoi
3bb18d0baf fix: precision issue in production plan (#53370) 2026-03-12 14:26:29 +05:30
Krishna Shirsath
831b1d3a79 feat(employee): Refactor milestone indicator functions to accept 'today' parameter 2026-03-12 14:19:15 +05:30
Khushi Rawat
bef4c010c6 Merge pull request #53371 from khushi8112/asset-repair-show-general-ledger
fix: move show_general_ledger to Asset Repair form events
2026-03-12 14:05:20 +05:30
khushi8112
ac124bdc7e fix: move show_general_ledger to Asset Repair form events 2026-03-12 13:18:00 +05:30
Mihir Kandoi
9c0c39381f fix: add validation in bom creator function (#53364) 2026-03-12 06:48:54 +00:00
Mihir Kandoi
b1ff4daaf5 fix: NoneType error when template description is to be copied to variant (#53358) 2026-03-12 06:33:28 +00:00
Mihir Kandoi
cb05f8a67a refactor: supplier quotation comparision report button should start f… (#53361) 2026-03-12 06:15:47 +00:00
Mihir Kandoi
8d5566e783 refactor: add type hints required flag to hooks.py (#53356) 2026-03-12 11:42:46 +05:30
Mihir Kandoi
417066d188 refactor: add type hints required flag to hooks.py 2026-03-12 11:08:24 +05:30
Ejaaz Khan
e4d79c6246 fix: remove redundant pos print format (#53348) 2026-03-12 05:11:53 +00:00
David
a3a57e20f1 Feat/shipment default contact (#53029) 2026-03-12 10:40:39 +05:30
NaviN
a8dcf70459 fix(delivery note): avoid maintaining si_detail on return delivery note (#52456) 2026-03-12 10:36:18 +05:30
Pandiyan P
a30599570c fix: handle NoneType error while creating a item (#53313) 2026-03-12 10:31:41 +05:30
V Shankar
e107d3ca84 fix: re-calculate taxes and totals after resetting bundle item rate (#53342) 2026-03-12 10:29:59 +05:30
Pandiyan P
77cf0afa1a fix: update child item schedule_date and prevent past dates (#53298) 2026-03-12 09:57:38 +05:30
Shllokkk
56f597f5ad fix(email_campaign): prevent unsubscribing entire campaign when email group member unsubscribes 2026-03-12 00:54:40 +05:30
MochaMind
793db90a14 fix: Portuguese, Brazilian translations 2026-03-11 23:09:41 +05:30
Krishna Shirsath
e4a0d2ab0b feat(employee): Enhance milestone indicators for birthdays and work anniversaries 2026-03-11 22:38:42 +05:30
Deepesh Garg
9a4c7766e3 Merge pull request #53327 from Nihantra-Patel/fix-journal-entry-ignore-linked-doctypes
fix: Append existing ignored doctypes in Journal Entry on_cancel instead of overwriting
2026-03-11 16:10:37 +05:30
Mihir Kandoi
f1ac0376fb feat: co product by product support (#52979) 2026-03-11 14:46:36 +05:30
ruthra kumar
65c33e6b39 Merge pull request #53326 from ruthra-kumar/allow_cost_center_on_payment_entry_deductions
refactor: make cost center editable in payment entry deduction
2026-03-11 14:45:41 +05:30
Nihantra Patel
39e10c4ab0 fix: Append existing ignored doctypes in Journal Entry on_cancel instead of overwriting 2026-03-11 14:40:41 +05:30
ruthra kumar
38bdc99172 Merge pull request #53294 from ruthra-kumar/stop_v14_weekly_release
ci: drop v14 from weekly release
2026-03-11 14:38:38 +05:30
ruthra kumar
078b22d985 refactor: make cost center editable in payment entry deduction 2026-03-11 14:26:16 +05:30
Nishka Gosalia
0205341cb5 Merge pull request #53312 from creative-paramu/production_plan_item_description_update 2026-03-11 14:18:36 +05:30
nareshkannasln
fa34ebea94 fix: skip BudgetValidation when cancelling GL entries 2026-03-11 11:59:14 +05:30
Parameshwari Palanisamy
39e68a9ce7 Update production_plan.py 2026-03-11 11:41:24 +05:30
creative-paramu
19533551f4 fix: update item description in Production Plan Assembly Items table 2026-03-11 11:14:33 +05:30
MochaMind
f3b270c927 fix: Serbian (Cyrillic) translations 2026-03-10 23:06:36 +05:30
MochaMind
330216ff0d fix: Serbian (Latin) translations 2026-03-10 23:06:26 +05:30
MochaMind
86bad84504 fix: Swedish translations 2026-03-10 23:06:18 +05:30
MochaMind
24e0c4034d fix: Spanish translations 2026-03-10 23:06:09 +05:30
Shllokkk
17eb983c40 fix: sanitize genericode import inputs and secure XML parser 2026-03-10 21:15:33 +05:30
ruthra kumar
ac8f3d7f96 fix: set default list view columns and filters for sales, purchase and accounts module (#53256) 2026-03-10 17:52:17 +05:30
Abdeali Chharchhodawala
0d42faac2e Merge pull request #52636 from Abdeali099/Abdeali/india-coa
refactor: enhance chart of accounts for India with account categories
2026-03-10 17:21:37 +05:30
ruthra kumar
7cf7a967da Merge pull request #53071 from aerele/fix-gross-profit-qty-precision
fix(gross-profit): apply precision-based rounding to grouped totals
2026-03-10 16:37:41 +05:30
ruthra kumar
ee187065c6 ci: drop v14 from weekly release 2026-03-10 16:29:02 +05:30
rohitwaghchaure
ac8a0b7b3d Merge pull request #53283 from rohitwaghchaure/fixed-patch-not-exists
fix: removed non existent patch
2026-03-10 14:25:50 +05:30
Mihir Kandoi
567d6c5102 Merge pull request #53282 from mihir-kandoi/fix-qi-submission 2026-03-10 14:15:32 +05:30
rohitwaghchaure
20cdf744fd Merge pull request #53281 from rohitwaghchaure/fixed-better-validation-message-pi-with-update-stock
fix: better validation message for Purchase Invoice with Update Stock
2026-03-10 14:03:18 +05:30
Mihir Kandoi
9f62ec5192 fix: allow user to make QI after submission not working 2026-03-10 13:55:49 +05:30
Rohit Waghchaure
c4b3080eae fix: removed non existent patch 2026-03-10 13:55:36 +05:30
Rohit Waghchaure
cfb06cf247 fix: better validation message for Purchase Invoice with Update Stock 2026-03-10 13:36:11 +05:30
Mihir Kandoi
53238ba94f Merge pull request #53203 from aerele/employee-user-status 2026-03-10 13:09:08 +05:30
Mihir Kandoi
1da71fee2b Merge pull request #53235 from aerele/fix/sales-order-delivery-date 2026-03-10 12:52:53 +05:30
Priyal Rawal
a6e78c2eea fix: add permission checks in whitelisted functions (#53103) 2026-03-10 07:04:15 +00:00
ruthra kumar
f2f47d6d88 Merge pull request #53238 from vorasmit/regional-rounding
fix: use return value of `get_round_off_applicable_accounts`
2026-03-10 12:28:16 +05:30
rohitwaghchaure
8d470b92db Merge pull request #53272 from rohitwaghchaure/fixed-patch-falling
fix: patch failing
2026-03-10 12:01:42 +05:30
Rohit Waghchaure
6024c4a077 fix: patch failing 2026-03-10 11:44:20 +05:30
MochaMind
b0ec75d539 fix: sync translations from crowdin (#53262) 2026-03-09 20:49:12 +01:00
ruthra kumar
cd7845124c fix: allow payment_request to be created in draft (#53160) 2026-03-09 20:21:33 +05:30
Shllokkk
2e844a58fb perf: optimize account balance data fetching for Chart Of Accounts 2026-03-09 20:09:03 +05:30
rohitwaghchaure
5ca8641488 Merge pull request #53254 from rohitwaghchaure/fixed-github-53253
fix: bom UX issues
2026-03-09 18:17:33 +05:30
Nabin Hait
9cf529215f fix: set default list view columns and filters for sales, purchase and accounts module 2026-03-09 17:20:52 +05:30
Rohit Waghchaure
32447b8204 fix: bom UX issues 2026-03-09 16:25:08 +05:30
Krishna Shirsath
be819eb876 fix(employee): correct work anniversary message pluralization 2026-03-09 16:06:55 +05:30
Nishka Gosalia
af817b8134 Merge pull request #53249 from nishkagosalia/purchase-order-button-fix 2026-03-09 15:05:42 +05:30
Nishka Gosalia
829dbbe12b fix :buttons not visible on purchase and sales order 2026-03-09 15:02:31 +05:30
Khushi Rawat
9c27d66add Merge pull request #53242 from khushi8112/asset-report-query-builder
refactor: replace raw SQL with query builder in asset depreciation and balances report
2026-03-09 14:57:29 +05:30
sudarshan-g
47772f4e77 feat: add cost center field to the stock entry accounting dimension tab 2026-03-09 13:32:59 +05:30
rohitwaghchaure
563184920a Merge pull request #53239 from rohitwaghchaure/fixed-validation-for-docstatus
fix: validation for cancellation
2026-03-09 13:18:56 +05:30
Pandiyan37
77367b5517 fix(selling): update delivery date in line items 2026-03-09 13:10:01 +05:30
Krishna Pramod Shirsath
2b8a4a9b5f Merge branch 'frappe:develop' into feat/employee-milestone-indicators 2026-03-09 13:06:20 +05:30
Krishna Shirsath
7e8a830f42 feat(employee): Add birthdays and work anniversaries indicator in form 2026-03-09 13:01:52 +05:30
khushi8112
f496995415 refactor: more functions in same file 2026-03-09 12:56:24 +05:30
Krishna Shirsath
1f19175fef refactor(employee): remove anniversary indicator logic from employee form 2026-03-09 12:50:56 +05:30
khushi8112
314c882f3b refactor: replace raw SQL with query builder in asset depreciation and balannces report 2026-03-09 12:39:49 +05:30
Mihir Kandoi
adbe856c4e Merge pull request #53234 from aerele/fix/support-#61532 2026-03-09 12:33:46 +05:30
Rohit Waghchaure
8de272a8a1 fix: validation for cancellation 2026-03-09 12:05:46 +05:30
Krishna Pramod Shirsath
f8f61f0b81 Merge branch 'develop' into feat/employee-creation-and-lifecycle 2026-03-09 11:47:19 +05:30
Krishna Shirsath
053242d5bd fix(employee): add 'set_only_once' property to 'Create User Automatically' field 2026-03-09 11:12:12 +05:30
Smit Vora
56a073955a test: regional account correctly added to round off accounts 2026-03-09 06:19:42 +05:30
Smit Vora
5c6056e76b fix: use return value of get_round_off_applicable_accounts 2026-03-09 05:19:22 +05:30
diptanilsaha
bf38dea95f Merge pull request #53233 from frappe/pot_develop_2026-03-08 2026-03-08 22:56:51 +05:30
Sudharsanan11
ae9ff767fa feat(manufacturing): show disassembled qty in progress bar 2026-03-08 19:14:41 +05:30
Sudharsanan11
8027f5aafd fix(manufacturing): show returned qty in progress bar 2026-03-08 19:14:41 +05:30
frappe-pr-bot
cad6956935 chore: update POT file 2026-03-08 09:42:56 +00:00
ruthra kumar
d038ad2350 Merge pull request #53227 from ruthra-kumar/add_party_filter_on_comparison_report
refactor: party type and party filter for comparison report
2026-03-07 18:18:58 +05:30
ruthra kumar
b6f9c0844e refactor: party type and party filter for comparison report 2026-03-07 18:02:56 +05:30
diptanilsaha
f67cfc48e0 Merge pull request #53224 from diptanilsaha/missed_ta 2026-03-07 15:23:50 +05:30
diptanilsaha
56345f4354 refactor(book_appointment): type annotations for whitelisted methods 2026-03-07 15:00:34 +05:30
diptanilsaha
b51c59d20d refactor(templates): type annotations for whitelisted methods 2026-03-07 15:00:34 +05:30
diptanilsaha
decd9343ee refactor(call_log): type annotations for whitelisted methods 2026-03-07 15:00:34 +05:30
diptanilsaha
950bf682c3 refactor(support): type annotations for whitelisted methods 2026-03-07 15:00:34 +05:30
diptanilsaha
1f4d20413e refactor(serial_no): added missing type annotation on auto_fetch_serial_number 2026-03-07 15:00:34 +05:30
diptanilsaha
11a6db32ae refactor(leaderboard): type annotations for whitelisted methods 2026-03-07 15:00:34 +05:30
diptanilsaha
805fc807a9 refactor(work_order): added type annotations on cancel_stock_reservation_entries 2026-03-07 15:00:34 +05:30
diptanilsaha
b4fb74c84d refactor(asset): added type annotations on get_depreciation_rate 2026-03-07 15:00:28 +05:30
diptanilsaha
ac351bce4b Merge pull request #53222 from frappe/l10n_develop 2026-03-07 14:55:12 +05:30
diptanilsaha
208112e7a9 refactor(payment_request): type annotations for whitelisted methods 2026-03-07 11:44:16 +05:30
diptanilsaha
616bae1f84 refactor: type annotations for whitelisted methods 2026-03-07 11:42:16 +05:30
diptanilsaha
573eb25d5b Merge pull request #53022 from diptanilsaha/crm_ta 2026-03-07 11:41:26 +05:30
diptanilsaha
8267482ee9 refactor(crm): type annotations for whitelisted methods 2026-03-07 03:00:54 +05:30
diptanilsaha
a1c378c16f Merge pull request #53032 from diptanilsaha/setup_ta 2026-03-07 02:29:29 +05:30
diptanilsaha
e6bebbfe81 test: added the currency key and fixed company 2026-03-07 02:05:54 +05:30
MochaMind
9358a122cd fix: Russian translations 2026-03-06 23:08:42 +05:30
rohitwaghchaure
3bd023d640 Merge pull request #53218 from rohitwaghchaure/fixed-item-creation-in-test-cases
fix: HRMS test cases failing due to validation in item
2026-03-06 17:54:36 +05:30
Rohit Waghchaure
6702506f58 fix: HRMS test cases failing due to validation in item 2026-03-06 17:35:12 +05:30
diptanilsaha
913ce9454e fix(consolidated_financial_statement): convert report to presentation_currency if valid filter is set 2026-03-06 16:04:28 +05:30
diptanilsaha
24f5fb7b44 fix(sales_order): set valid target.currency in make_purchase_order 2026-03-06 16:04:28 +05:30
diptanilsaha
6aaca38ce3 refactor(setup): type annotataions for whitelisted methods 2026-03-06 16:04:28 +05:30
diptanilsaha
fbdb3a1f48 Merge pull request #53031 from diptanilsaha/regional_ta 2026-03-06 15:54:05 +05:30
Shubh Doshi
d2e039ad4e fix: add type hints to whitelisted functions (#53210) 2026-03-06 08:27:01 +00:00
rohitwaghchaure
d6abbce4ec Merge pull request #53200 from rohitwaghchaure/fixed-stock-balance-report
fix: stock balance report qty
2026-03-06 12:43:55 +05:30
Poovitha Palanivelu
194d060f13 fix: update user status depends on employee status 2026-03-06 12:27:24 +05:30
rohitwaghchaure
f316229f9e Merge pull request #52549 from rohitwaghchaure/feat-option-to-enable-disable-serial-batch
feat: option to enable serial / batch feature
2026-03-06 12:17:11 +05:30
Rohit Waghchaure
a15e5fdc4e fix: stock balance report qty 2026-03-06 12:16:07 +05:30
Rohit Waghchaure
82c3da5b1e feat: option to enable serial / batch features 2026-03-06 11:47:53 +05:30
Soham Kulkarni
76d8e8fec9 Merge pull request #53177 from sokumon/add-clear-demo-data 2026-03-05 23:43:21 +05:30
Akhil Narang
702adda000 fix(help): escape query (#53192)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2026-03-05 13:06:09 +00:00
rohitwaghchaure
2bc3c146a6 Merge pull request #52956 from rohitwaghchaure/organization-desktop-icon
feat: organization desktop icon
2026-03-05 14:05:32 +05:30
rohitwaghchaure
ba05608ce6 Merge pull request #52745 from rohitwaghchaure/fixed-balance-qty-inv-dimension
fix: balance qty for inv dimension
2026-03-05 13:49:36 +05:30
Rohit Waghchaure
4e9a2a327f feat: organization desktop icon 2026-03-05 13:43:35 +05:30
rohitwaghchaure
06e0effbf7 Merge pull request #53170 from jacob-salvi/new-org-icon
chore: New org icon
2026-03-05 13:39:16 +05:30
Rohit Waghchaure
a3eafe5b18 fix: balance qty for inv dimension 2026-03-05 13:22:45 +05:30
Mihir Kandoi
50d6971d20 Merge pull request #53161 from mihir-kandoi/type-hint-issues 2026-03-05 12:06:26 +05:30
Nishka Gosalia
7e368c16b7 Merge pull request #52924 from nishkagosalia/projects-form-cleanup 2026-03-05 11:57:43 +05:30
Jatin3128
edac82f6e3 Merge pull request #52138 from aerele/payment-date-mismatch
feat(selling-settings): add checkbox to fetch payment terms
2026-03-05 11:48:43 +05:30
Nishka Gosalia
3c9f520e68 refactor: project module form cleanup 2026-03-05 11:40:22 +05:30
sokumon
6603005822 fix: add clear demo data in sidebar 2026-03-05 11:32:56 +05:30
Nishka Gosalia
15eb5d9827 Merge pull request #52415 from aerele/project-template-task-subject 2026-03-05 11:31:21 +05:30
ravibharathi656
7801dd5353 fix(task): allow is_template field in quick entry 2026-03-05 10:56:48 +05:30
ravibharathi656
c1431105f4 fix(project template): clear subject when task is empty 2026-03-05 10:56:48 +05:30
jacob-salvi
6f93210b9b chore: new organisation icon 2026-03-05 10:14:45 +05:30
SowmyaArunachalam
8aae46a25e test: test payment terms with backdated entries 2026-03-04 21:47:43 +05:30
SowmyaArunachalam
99ed1c34f3 fix: fetch payment terms from quotation 2026-03-04 21:47:41 +05:30
Mihir Kandoi
f07a6eb199 Merge pull request #53166 from mihir-kandoi/wo-producted-qty 2026-03-04 20:33:57 +05:30
Mihir Kandoi
5fead1d17a fix: WO produced qty should be calculated using finished item child table transfer qty 2026-03-04 20:17:37 +05:30
SowmyaArunachalam
8b9e02fd44 fix: set default to 1 2026-03-04 19:00:16 +05:30
SowmyaArunachalam
f23dc07914 fix: handle payment terms template when disabled 2026-03-04 19:00:15 +05:30
SowmyaArunachalam
5611e9168e test: enable automatically_fetch_payment_terms_from_quotation 2026-03-04 19:00:15 +05:30
SowmyaArunachalam
b1149fe950 fix: consider payment term only when enabled 2026-03-04 19:00:15 +05:30
SowmyaArunachalam
70b401e610 feat(selling-settings): add checkbox to recalculate payment date 2026-03-04 19:00:15 +05:30
Mihir Kandoi
58c198a58b Merge pull request #53157 from mihir-kandoi/gh53101 2026-03-04 16:51:09 +05:30
Khushi Rawat
f2decc852c Merge pull request #53154 from khushi8112/inter-company-asset-transfer-issue
fix: skip asset sale processing for internal transfer invoices
2026-03-04 16:36:03 +05:30
Mihir Kandoi
ee19c32c3a fix: disallow all actions on job card if work order is closed 2026-03-04 16:34:32 +05:30
Mihir Kandoi
74b658fabf fix: some type hints 2026-03-04 16:31:28 +05:30
ervishnucs
24825a16e0 fix: allow payment_request to be created in draft 2026-03-04 16:25:30 +05:30
Mihir Kandoi
083b571641 Merge pull request #53156 from nishkagosalia/st-61597 2026-03-04 16:12:24 +05:30
Nishka Gosalia
e37d4a6f7c fix: updating costing based on employee change in timesheet 2026-03-04 15:24:54 +05:30
khushi8112
9cb3dad079 fix: skip asset sale processing for internal transfer invoices 2026-03-04 14:20:14 +05:30
Mihir Kandoi
700c14d5b3 Merge pull request #53147 from nishkagosalia/gh-52102 2026-03-04 12:19:35 +05:30
Mihir Kandoi
c0148c7266 Merge pull request #53148 from mihir-kandoi/fg_completed_qty 2026-03-04 12:09:18 +05:30
Mihir Kandoi
2d83069b82 fix: do not update fg_completed_qty when changing qty of fg line item 2026-03-04 12:06:28 +05:30
Nishka Gosalia
2ec02e477f feat: allowing rate modification in update item in quotation 2026-03-04 10:54:51 +05:30
ruthra kumar
42ae954c16 Merge pull request #53005 from frappe/l10n_develop
fix: sync translations from crowdin
2026-03-04 10:45:47 +05:30
Mihir Kandoi
e3a55d31d3 Merge pull request #52784 from aerele/fix/support-#60042 2026-03-03 21:16:15 +05:30
Mihir Kandoi
767925a481 Merge pull request #53132 from aerele/fix/selling-price-validation 2026-03-03 21:09:30 +05:30
Mihir Kandoi
e970578c47 Merge pull request #53070 from aerele/rfq-email-template 2026-03-03 21:05:53 +05:30
kavin-114
723993fdf6 test: add unit test for FG Item selling price validation 2026-03-03 20:44:08 +05:30
kavin-114
4335318482 fix(selling): handle selling price validation for FG item
System checks valuation rate in incoming_rate field, since SO has it in valuation_rate field of item row,
need to handle the field name dynamically based upon the doctype name in Selling Controller.
2026-03-03 20:44:00 +05:30
Mihir Kandoi
17c715fde0 Merge pull request #53084 from aerele/fix/support-#61033 2026-03-03 20:40:31 +05:30
Mihir Kandoi
c76be620a8 Merge pull request #53037 from aerele/fix/qi-naming-rule-issue 2026-03-03 20:38:54 +05:30
Mihir Kandoi
2ce276df5b Merge pull request #53099 from aerele/fix/procurement-report-requestor-invalid 2026-03-03 20:38:10 +05:30
rohitwaghchaure
e1bad10e6c Merge pull request #53123 from rohitwaghchaure/fixed-status-of-serial-no
fix: serial no status for Disassemble entry
2026-03-03 18:53:35 +05:30
Shariq Ansari
93f2a8ad6b Merge pull request #53119 from shariquerik/erpnext-crm-fix 2026-03-03 03:42:52 -08:00
Rohit Waghchaure
81acefa8ad fix: serial no status for Disassemble entry 2026-03-03 17:11:53 +05:30
shariquerik
800810d23d fix: ensure contacts are processed only if present in create_prospect_against_crm_deal 2026-03-03 16:56:00 +05:30
diptanilsaha
a1124449c2 Merge pull request #53110 from diptanilsaha/pr_tt 2026-03-03 15:00:09 +05:30
diptanilsaha
6342e78305 refactor: renamed args to pricing_ctx in set_transaction_type for clarity 2026-03-03 14:42:11 +05:30
Vishnu Priya Baskaran
d2e04750b5 fix: avoid circular dependency (#53109) 2026-03-03 14:32:08 +05:30
diptanilsaha
7ec0354a79 fix(pricing_rule): strict validation of transaction_type 2026-03-03 14:08:49 +05:30
Ravibharathi
9a5b476d9c Merge pull request #51940 from aerele/accounts-receivable-payment-terms-template-filter 2026-03-03 11:27:31 +05:30
diptanilsaha
7ee08c9964 Merge pull request #53104 from frappe/ch-sec 2026-03-03 10:38:15 +05:30
diptanilsaha
b666d9d5c1 chore: update SECURITY.md 2026-03-03 10:36:54 +05:30
kavin-114
bdf4e51da3 fix: populate mr owner and set po owner as fallback 2026-03-02 23:40:40 +05:30
kavin-114
397de1274f fix: pass company in test case using make_quality_inspections 2026-03-02 22:50:21 +05:30
kavin-114
4c39cf2d65 test: add unit test to handle company condition in naming rule 2026-03-02 22:50:11 +05:30
rohitwaghchaure
260fc6a7ce Merge pull request #53093 from rohitwaghchaure/fixed-opening-qty-in-stock-balance
fix: opening qty in stock balance
2026-03-02 20:48:51 +05:30
Mihir Kandoi
c031e345db Merge pull request #52838 from Shllokkk/so-mr-po 2026-03-02 20:41:20 +05:30
Rohit Waghchaure
d7fdab99cb fix: opening qty in stock balance 2026-03-02 17:44:13 +05:30
Shllokkk
35d814c7b3 Merge branch 'develop' into so-mr-po 2026-03-02 16:43:13 +05:30
Shllokkk
91c6475f1c fix: update select query field in patch and code refactor 2026-03-02 16:25:02 +05:30
rohitwaghchaure
735631899f Merge pull request #53087 from aerele/perf/index-reference-purchase-receipt-column
perf: add index on reference_purchase_receipt column
2026-03-02 14:37:08 +05:30
rohitwaghchaure
868a7eb5ca Merge pull request #53082 from rohitwaghchaure/fixed-validate-warehouse-of-sabb
fix: validate warehouse of SABB for draft entry
2026-03-02 14:21:56 +05:30
kavin-114
8c94396ad9 perf: add index on reference_purchase_receipt column 2026-03-02 14:05:40 +05:30
rohitwaghchaure
47b08eaa41 Merge pull request #53072 from aerele/fix/support-#61417
fix(accounts): set posting time to get incoming rate
2026-03-02 13:45:49 +05:30
Rohit Waghchaure
9b8f685c82 fix: validate warehouse of SABB for draft entry 2026-03-02 13:41:20 +05:30
Sudharsanan11
c5b3673a30 fix(accounts): set posting time to get incoming rate 2026-03-02 13:21:25 +05:30
Sudharsanan11
9538a9870c fix(accounts): add transaction time field 2026-03-02 13:21:21 +05:30
Sudharsanan11
6b1aac4aee fix(manufacturing): ignore sales order validation for subassembly item 2026-03-02 12:26:55 +05:30
Diptanil Saha
066442c236 Merge pull request #53079 from diptanilsaha/bvr-validate-dimension 2026-03-02 12:00:37 +05:30
Navin-S-R
52dd7665e7 fix(gross-profit): apply precision-based rounding to grouped totals 2026-03-02 11:15:49 +05:30
diptanilsaha
79d0708ea7 fix(budget-variance-report): validate 'budget_against' filter 2026-03-02 11:04:35 +05:30
Nishka Gosalia
7b3d85c74e Merge pull request #53047 from trustedcomputer/fix-timesheet-table-read-only 2026-03-02 10:54:44 +05:30
rohitwaghchaure
733528a9f5 Merge pull request #53050 from rohitwaghchaure/fixed-incorrect-voucher-detail-no
fix: voucher detail no in SABB
2026-03-02 09:52:48 +05:30
Pugazhendhi Velu
49d363b174 fix: handle html email template separately in RFQ to avoid jinja context error 2026-03-01 18:21:09 +00:00
Rohit Waghchaure
c37a56ec89 fix: voucher detail no in SABB 2026-03-01 21:54:00 +05:30
Mihir Kandoi
1592229b24 Merge pull request #53067 from mihir-kandoi/correct-precision 2026-03-01 21:21:10 +05:30
Mihir Kandoi
36726b0f7b fix: use the correct precision value in stock reco 2026-03-01 20:48:33 +05:30
Mihir Kandoi
3fad5fb9b9 Merge pull request #53062 from Sanjesh2254/fix/sle-voucher-type-comparison 2026-03-01 20:46:01 +05:30
Sanjesh
cffb59ae73 fix: correct sle voucher_type comparison in get_ref_doctype 2026-03-01 16:31:53 +05:30
Mihir Kandoi
9563af9ae1 Merge pull request #53057 from mihir-kandoi/gh53003 2026-03-01 15:47:12 +05:30
Mihir Kandoi
e68d9af498 Merge pull request #53052 from mihir-kandoi/gh53035 2026-03-01 15:20:55 +05:30
Mihir Kandoi
04127019f9 fix: allow allowed roles to bypass over billing validation 2026-03-01 15:16:38 +05:30
Mihir Kandoi
81fd3c86f4 Merge pull request #53051 from mihir-kandoi/gh53049 2026-03-01 15:09:29 +05:30
Mihir Kandoi
30c3ff2efe fix: use stock qty instead of qty when creating stock entry from MR 2026-03-01 15:04:17 +05:30
Mihir Kandoi
5f12b0db3f fix: use conversion factor when creating stock entry from pick list 2026-03-01 14:53:27 +05:30
trustedcomputer
b9d95711a2 fix: remove read-only property from Sales Invoice Timesheet Table 2026-02-28 18:10:07 -08:00
MochaMind
86ade52dc6 fix: Spanish translations 2026-02-28 22:26:03 +05:30
kavin-114
74def423ed fix(stock): pass company to avoid document naming rule issue in QI
When a Document Naming Rule is configured in QI with a condition based on the company,
the system was not passing the company value properly. As a result, the naming rule
condition was skipped and the document name was generated using the default series.

This fix ensures that the company is passed correctly so that the configured
Document Naming Rule is evaluated and applied as expected.
2026-02-28 13:10:30 +05:30
rohitwaghchaure
061d2e45a9 Merge pull request #52988 from rohitwaghchaure/fixed-same-reposting-picked-by-multiple-jobs
fix: same reposting entry picked by multiple rq jobs
2026-02-28 07:35:44 +05:30
diptanilsaha
df6780bf1a refactor(regional): type annotataions for whitelisted methods 2026-02-28 02:52:03 +05:30
Khushi Rawat
6fc0ea02a1 Merge pull request #53004 from khushi8112/tax-rule-validation-query-builder
refactor: use query builder for Tax Rule validation query
2026-02-27 23:54:03 +05:30
MochaMind
81281d88c7 fix: Serbian (Cyrillic) translations 2026-02-27 22:21:42 +05:30
MochaMind
3dcdde83c7 fix: Serbian (Latin) translations 2026-02-27 22:21:36 +05:30
MochaMind
27381ae77b fix: Swedish translations 2026-02-27 22:21:32 +05:30
MochaMind
63fe68bf28 fix: Spanish translations 2026-02-27 22:21:25 +05:30
Diptanil Saha
6fbdcc3dd1 Merge pull request #53028 from diptanilsaha/qm_ta 2026-02-27 17:06:37 +05:30
ruthra kumar
8397b1ee4b Merge pull request #53027 from ruthra-kumar/remove_frappe_flags_from_dimension_filter
refactor: remove frappe.flags from accounting dimensions filter
2026-02-27 16:54:56 +05:30
Diptanil Saha
a5e4256491 Merge pull request #53026 from diptanilsaha/project_ta 2026-02-27 16:50:36 +05:30
diptanilsaha
eb2f986b11 refactor(quality_management): type annotations for whitelisted methods 2026-02-27 16:48:58 +05:30
Diptanil Saha
5bae06ae96 Merge pull request #53024 from diptanilsaha/maintenance_ta 2026-02-27 16:41:39 +05:30
Diptanil Saha
2712bc8661 Merge pull request #53023 from diptanilsaha/ref_erp_int 2026-02-27 16:40:49 +05:30
ruthra kumar
290f979fd3 refactor: remove frappe.flags from accounting dimensions filter 2026-02-27 16:37:27 +05:30
diptanilsaha
a22ea1de86 refactor(project): type annotations for whitelisted methods 2026-02-27 16:34:04 +05:30
diptanilsaha
13859dfb3c refactor(erpnext_integrations): type annotations for whitelisted methods 2026-02-27 16:19:40 +05:30
diptanilsaha
3078c4a451 fix: get_serial_nos_from_schedule return value 2026-02-27 16:11:45 +05:30
diptanilsaha
73f72bda42 refactor(maintenance): type annotations for whitelisted methods 2026-02-27 16:00:20 +05:30
rohitwaghchaure
aec400c12d Merge pull request #53013 from rohitwaghchaure/revert-stock-reco-issue
fix: old stock reco entries causing issue in the stock balance report
2026-02-27 14:56:07 +05:30
Rohit Waghchaure
0874cbc268 fix: old stock reco entries causing issue in the stock balance report 2026-02-27 14:09:15 +05:30
Mihir Kandoi
048608556c Merge pull request #53012 from thomasantony12/patch-2 2026-02-27 13:53:07 +05:30
Krishna Pramod Shirsath
66976c13e4 Merge branch 'develop' into feat/employee-creation-and-lifecycle 2026-02-27 13:10:24 +05:30
Diptanil Saha
397d806f0f Merge pull request #53014 from diptanilsaha/tb_ctb 2026-02-27 12:55:13 +05:30
Krishna Shirsath
124ec4d3c2 fix: add missing type hints to whitelisted function arguments 2026-02-27 12:53:44 +05:30
diptanilsaha
61e01aff67 fix(consolidated-trial=balance): total calculation with show_group_accounts and show_net_values filters 2026-02-27 12:26:20 +05:30
diptanilsaha
560c2511cc feat(consolidated-trial-balance): introduced show_net_values filter 2026-02-27 12:24:47 +05:30
diptanilsaha
f582fdbf71 fix(trial-balance): totals with filter show_group_accounts enabled 2026-02-27 12:21:56 +05:30
ruthra kumar
ffa6280431 Merge pull request #53011 from ruthra-kumar/remove_frappe_flags_from_acc_dimension
refactor: use cache decorator in accounting dimensions
2026-02-27 11:48:06 +05:30
ruthra kumar
565542879e refactor: remove frappe.flags and make use of cache decorator 2026-02-27 11:30:52 +05:30
Thomas antony
b33f06701c feat: UOM query filter for opportunity items
Add UOM query filter based on item code in opportunity form.
2026-02-27 11:11:28 +05:30
MochaMind
1811bb7643 fix: Serbian (Latin) translations 2026-02-26 22:23:42 +05:30
MochaMind
94ea72948e fix: Persian translations 2026-02-26 22:23:37 +05:30
MochaMind
cfda569af1 fix: Swedish translations 2026-02-26 22:23:29 +05:30
MochaMind
4b72ac7d22 fix: Spanish translations 2026-02-26 22:23:21 +05:30
khushi8112
235e3adbcb refactor: use query builder for Tax Rule validation query 2026-02-26 17:18:40 +05:30
Diptanil Saha
c57e452efd Merge pull request #52997 from diptanilsaha/pe-correct-precisions 2026-02-26 14:53:50 +05:30
Rohit Waghchaure
4dfbacfd92 fix: same reposting entry picked by multiple rq jobs 2026-02-26 14:34:39 +05:30
diptanilsaha
d82a0a9455 fix(payment_entry): fix precision for total_allocated_amount and base_total_allocated_amount
Co-authored-by: Ahuahuachi <Ahuahuachi@users.noreply.github.com>
2026-02-26 14:33:50 +05:30
ruthra kumar
c2ed02d4e4 Merge pull request #52630 from mendozal/fix-compute-tax-net-amount-in-js
fix(account): compute tax net_amount in JS controller
2026-02-26 13:47:27 +05:30
Luis Mendoza
b10b205394 fix(accounts): round and convert net_amount to company currency in JS tax controller 2026-02-26 08:09:53 +00:00
Luis Mendoza
485166b668 style: prettier formatting 2026-02-26 08:09:53 +00:00
Luis Mendoza
153ad99f85 fix(accounts): compute tax net_amount in JS controller 2026-02-26 08:09:53 +00:00
ruthra kumar
2d07b346de Merge pull request #52970 from ljain112/fix-missing-type-hint
fix: update type hint for party parameter to allow None in get_party_details and set_taxes functions
2026-02-26 13:32:43 +05:30
ruthra kumar
56732df641 Merge pull request #52188 from aerele/payment-entry-float-precision
fix(payment entry): round unallocated amount
2026-02-26 13:07:33 +05:30
Shllokkk
8c46e06e04 fix: correct get linked payments type hint in bank reconcilliation tool (#52982)
fix: correct type hint for bank reconcilliation tool
2026-02-26 12:41:40 +05:30
ravibharathi656
b0d6751777 fix(payment entry): round unallocated amount 2026-02-26 12:25:44 +05:30
rohitwaghchaure
5d46e1a7b1 Merge pull request #52983 from rohitwaghchaure/fixed-asset-broken-link
fix: broken link of docs in asset onboarding
2026-02-26 11:05:43 +05:30
Rohit Waghchaure
1cdf439e38 fix: broekn link of docs in asset onboarding 2026-02-26 10:47:56 +05:30
Mihir Kandoi
4c374e182d Merge pull request #52952 from nishkagosalia/gh-52947 2026-02-25 20:59:15 +05:30
Diptanil Saha
4c7175fad1 Merge pull request #52973 from ljain112/fix-fiscal-year
fix: ensure cache is cleared on fiscal year update and trash
2026-02-25 20:05:23 +05:30
ljain112
39b0e3522a fix: ensure cache is cleared on fiscal year update and trash 2026-02-25 19:27:52 +05:30
ljain112
f220960d91 fix: update type hint for party parameter to allow None in get_party_details and set_taxes functions 2026-02-25 18:01:24 +05:30
rohitwaghchaure
7ef187b1eb Merge pull request #52967 from rohitwaghchaure/patch-for-existing-records
fix: patch to complete onboarding stpes for existing records
2026-02-25 17:59:17 +05:30
Rohit Waghchaure
d90ec49241 fix: patch to complete onboarding stpes for existing records 2026-02-25 17:23:57 +05:30
rohitwaghchaure
2517369270 Merge pull request #52957 from rohitwaghchaure/fixed-parallel-reposting
fix: reposting creation slow for GL entries
2026-02-25 16:03:26 +05:30
Rohit Waghchaure
d9ac198bad fix: reposting creation slow for GL entries 2026-02-25 14:23:38 +05:30
ruthra kumar
cf09f725ca Merge pull request #52910 from Shllokkk/accounts-type-hints
refactor(accounts): add type hints for whitelisted functions
2026-02-25 13:25:31 +05:30
Krishna Shirsath
4f43f655cf feat(employee): Add birthdays and work anniversaries indicator in form ,list view enhancements and new empty state. 2026-02-25 13:21:06 +05:30
Nishka Gosalia
a37ffe6e2a Merge pull request #52948 from nishkagosalia/fix-type-hints 2026-02-25 13:12:13 +05:30
Nishka Gosalia
dd0bcf4dbd fix: type hint in stock 2026-02-25 12:54:10 +05:30
Nishka Gosalia
1d4c835843 fix: customer field made mandatory for sales invoice 2026-02-25 12:43:20 +05:30
Shllokkk
61661a159d fix: correct type hints 2026-02-25 12:30:52 +05:30
Mihir Kandoi
1e297a0e5f Merge pull request #52945 from mihir-kandoi/fix-item-link-formatter 2026-02-25 10:25:38 +05:30
Mihir Kandoi
9ef7f05712 fix: item code shows undefined 2026-02-25 10:19:10 +05:30
Mihir Kandoi
10257bbb6e Merge pull request #52942 from mihir-kandoi/gh52359 2026-02-25 10:12:45 +05:30
Mihir Kandoi
bd9e5e97d7 chore: clearer description for internal transfer at arms length 2026-02-25 09:57:13 +05:30
ruthra kumar
bf8b730259 Merge pull request #52940 from frappe/l10n_develop
fix: sync translations from crowdin
2026-02-25 08:49:47 +05:30
MochaMind
cecea63627 fix: Persian translations 2026-02-24 22:28:22 +05:30
MochaMind
d6888a32ae fix: Portuguese, Brazilian translations 2026-02-24 22:28:15 +05:30
MochaMind
1a6a16be5b fix: Swedish translations 2026-02-24 22:28:02 +05:30
MochaMind
dced32088c fix: Spanish translations 2026-02-24 22:27:22 +05:30
Khushi Rawat
bb7fdd59dc Merge pull request #52889 from khushi8112/default-print-format-and-letterhead
feat: default letterhead and print format
2026-02-24 21:21:36 +05:30
Mihir Kandoi
c2a1dbeb67 Merge pull request #52724 from ljain112/fix-subcontracting-ctrl 2026-02-24 20:37:09 +05:30
khushi8112
8a2cb96c2a fix: test cases related to default letterhead change 2026-02-24 19:56:25 +05:30
ruthra kumar
7aa3dbcd86 Merge pull request #51777 from Jatin3128/payment-request-schedules-feat
feat: making payment requests based on payment schedule
2026-02-24 18:38:16 +05:30
khushi8112
570f574758 test: debugging the issue 2026-02-24 18:24:16 +05:30
khushi8112
fbf5529ddd fix: add missing property_type 2026-02-24 18:24:16 +05:30
khushi8112
0ea22f9796 feat: default letterhead and print format 2026-02-24 18:24:16 +05:30
Harsh Patadia
bdcb2c1512 refactor: separate construction of chart related data from get_columns() (#52824)
* fix: avoid hardcoded column slicing for Profit & Loss chart data

* refactor: improve parameter naming and reduce code repetion by using same function get_period_columns()

* refactor: improved parameter naming in get_data() and get_chart_data()
2026-02-24 12:36:47 +00:00
Nishka Gosalia
ddb497acb5 Merge pull request #52834 from nishkagosalia/stock-type-hints 2026-02-24 17:34:04 +05:30
rohitwaghchaure
539d1a4e42 Merge pull request #52930 from rohitwaghchaure/fixed-removed-form-tour-
fix: remove form tour for sales and purchase order
2026-02-24 17:23:33 +05:30
Nishka Gosalia
13995a64b8 refactor(stock): adding type hint for stock module 2026-02-24 17:10:57 +05:30
Rohit Waghchaure
ed7315d78e fix: remove form tour for sales and purchase order 2026-02-24 17:00:02 +05:30
Diptanil Saha
9541cb226d Merge pull request #52029 from ljain112/fix-inclusive-discount 2026-02-24 16:58:12 +05:30
Khushi Rawat
dd910bb4cc Merge pull request #52927 from khushi8112/fix-taxes-and-totals-type-hint
fix: type hint for get_round_off_applicable_accounts
2026-02-24 15:57:47 +05:30
khushi8112
2f3ac06eff fix: type hint for get_round_off_applicable_accounts 2026-02-24 15:36:41 +05:30
Jatin3128
e476dff842 feat(payment request): create payment request as per payment schedules 2026-02-24 07:04:16 +00:00
Jatin3128
60108590b0 feat(payment_request): add option to calculate request amount using payment schedule 2026-02-24 07:04:16 +00:00
Shllokkk
f7ff61be5d refactor(accounts): add type hints for whitelisted functions 2026-02-24 12:30:44 +05:30
rohitwaghchaure
a15e2ecf78 Merge pull request #52839 from rohitwaghchaure/feat-user-onboarding
feat: module onboarding
2026-02-24 12:25:06 +05:30
ruthra kumar
4ba43a538e fix: unhide book_advance_payments_in_separate_party_account check fie… (#52363)
fix: unhide book_advance_payments_in_separate_party_account check field in Payment Entry doctype
2026-02-24 11:51:39 +05:30
Sowmya
d638f3e033 fix(sales-order): update quotation status while cancelling sales order (#52822)
* fix(sales-order): update quotation status while cancelling sales order

* test: validate quotation status

* chore: remove submit
2026-02-24 11:47:48 +05:30
Rohit Waghchaure
792a1a7ab7 feat: module onboarding 2026-02-24 11:45:31 +05:30
rohitwaghchaure
ced0c94e8e fix: sales and purchase modules forms clean-up (#52875) 2026-02-23 23:35:08 +05:30
Rohit Waghchaure
0e356dc2e3 fix: sales and purchase modules forms clean-up 2026-02-23 23:05:05 +05:30
ruthra kumar
99a5886f3a Merge pull request #52896 from aerele/skip-empty-dimensions-exc-gain-loss
fix: skip empty dimension values in exchange gain loss
2026-02-23 21:06:00 +05:30
Mihir Kandoi
e5e164c536 Merge pull request #52905 from frappe/mergify/bp/develop/pr-52544 2026-02-23 20:59:03 +05:30
Mihir Kandoi
bde15cb64b Merge pull request #52417 from Shllokkk/quality-procedure-fix 2026-02-23 20:52:40 +05:30
Imesha Sudasingha
ae3453c84b Merge pull request #52544 from one-highflyer/fix/improve-reserved-serial-no-error-message
fix(stock): improve error message when serial no is reserved via SRE

(cherry picked from commit 71248ff40b)
2026-02-23 15:12:56 +00:00
Mihir Kandoi
a0c8ac524b Merge pull request #52764 from aerele/populate_doctypes_to_be_ignored_table 2026-02-23 20:20:29 +05:30
Diptanil Saha
30e0285ae7 Merge pull request #52891 from diptanilsaha/notifs 2026-02-23 19:37:52 +05:30
Mihir Kandoi
2f635a1021 Merge pull request #52892 from mihir-kandoi/gh52864 2026-02-23 19:06:17 +05:30
Mihir Kandoi
9a253c4f54 Merge pull request #52878 from mihir-kandoi/gh52832 2026-02-23 18:48:45 +05:30
ruthra kumar
68144c5be2 Merge pull request #52812 from aerele/fix-clerance-details
fix: bank account mismatch error on reverse transaction reconciliation
2026-02-23 18:05:55 +05:30
ravibharathi656
7df9d951c6 fix: skip empty dimension values in exchange gain loss 2026-02-23 18:01:10 +05:30
Mihir Kandoi
db00860662 fix: link field displays incorrect value when empty 2026-02-23 17:50:24 +05:30
Mihir Kandoi
a85a0aef52 fix: standalone sales invoice return should not fallback to item master for valuation rate 2026-02-23 17:48:46 +05:30
diptanilsaha
3e87059939 fix: fiscal year notification subject 2026-02-23 17:43:36 +05:30
diptanilsaha
96aa37eff5 fix: material request on receive notification condition 2026-02-23 17:43:08 +05:30
Diptanil Saha
a8b538cc67 Merge pull request #50301 from Abdeali099/fixing-emp-contacts 2026-02-23 17:39:46 +05:30
l0gesh29
60d2f2d304 fix: populate doctypes to be ignored table in validate 2026-02-23 17:20:54 +05:30
Sudharsanan Ashok
8b2a971019 fix(manufacturing): remove delete query of job card & batch and serial no (#52840)
* fix(manufacturing): remove delete query of batch and serial no

* fix(manufacturing): remove delete query of job card

* fix: remove delete function call for work order
2026-02-23 16:58:13 +05:30
Khushi Rawat
9478ccca27 Merge pull request #52861 from khushi8112/print-format-sales-order-purchase-invoice
feat: standard print format for Sales Order and Purchase Invoice
2026-02-23 16:26:48 +05:30
Khushi Rawat
9198bc1b8f Merge pull request #52884 from khushi8112/update-modified-timestamp-in-json
fix: update modified timestamp in json
2026-02-23 16:07:03 +05:30
khushi8112
7b3c10769b fix: update modified timestamp in json 2026-02-23 15:42:57 +05:30
Mihir Kandoi
a2afabab79 Merge pull request #52880 from aerele/fix-support/60562 2026-02-23 15:33:33 +05:30
Khushi Rawat
5e5487faae Merge pull request #52879 from mahsem/typo_recon
fix: typo
2026-02-23 15:23:52 +05:30
mahsem
2b72aab671 fix: typo 2026-02-23 10:34:30 +01:00
Pandiyan37
b7f45e6963 fix(work_order): update returned qty 2026-02-23 15:04:21 +05:30
khushi8112
cbea4493c1 refactor: add translation and fix typo 2026-02-23 14:17:08 +05:30
khushi8112
371efce88a feat: standard print format for Sales Order and Purchase Invoice 2026-02-23 14:17:08 +05:30
Khushi Rawat
f133b96cb4 Merge pull request #52823 from khushi8112/type-hints-for-controllers
refactor: add type hints to controller functions
2026-02-23 14:13:19 +05:30
Mihir Kandoi
853090729a Merge pull request #52871 from mihir-kandoi/gh52856 2026-02-23 13:22:38 +05:30
khushi8112
3677609838 refactor: correct type hint for rm_items 2026-02-23 13:08:26 +05:30
Mihir Kandoi
8e14249335 fix: use stock qty instead of qty when updating transferred qty in WO 2026-02-23 13:02:04 +05:30
khushi8112
e088a596c7 fix: remove unnecessary guest access from get_period_date_ranges 2026-02-23 12:49:46 +05:30
khushi8112
021aa63e24 fix: incorrect type hint 2026-02-23 12:49:46 +05:30
khushi8112
7100d61f3c refactor: add type hints to controller functions 2026-02-23 12:49:46 +05:30
ruthra kumar
3584d2bf23 Merge pull request #52852 from frappe/l10n_develop
fix: sync translations from crowdin
2026-02-23 11:01:57 +05:30
Mihir Kandoi
60e96633c4 Merge pull request #52803 from aerele/fix/support-#60342 2026-02-23 07:30:39 +05:30
Sudharsanan11
cfbdfcf515 test(manufacturing): add test to validate the planned qty 2026-02-23 02:32:40 +05:30
Sudharsanan11
d58171987c test(stock): add test to validate company for receipts and expense accounts 2026-02-23 02:16:21 +05:30
Sudharsanan11
d54d0c25a2 fix: set company based expense account 2026-02-23 02:16:21 +05:30
Sudharsanan11
15dfc08a31 fix(stock): validate company for receipt documents and expense accounts 2026-02-23 02:16:16 +05:30
MochaMind
e2682d2690 fix: Esperanto translations 2026-02-22 22:08:55 +05:30
MochaMind
ad511755ec fix: French translations 2026-02-22 22:08:52 +05:30
MochaMind
d790c60a93 fix: Serbian (Latin) translations 2026-02-22 22:08:49 +05:30
MochaMind
f53ea90113 fix: Norwegian Bokmal translations 2026-02-22 22:08:45 +05:30
MochaMind
a4652e0d5f fix: Bosnian translations 2026-02-22 22:08:42 +05:30
MochaMind
da16cc1727 fix: Burmese translations 2026-02-22 22:08:39 +05:30
MochaMind
a8fe1e24b3 fix: Croatian translations 2026-02-22 22:08:36 +05:30
MochaMind
a24efc6e34 fix: Thai translations 2026-02-22 22:08:33 +05:30
MochaMind
61f6daf55e fix: Persian translations 2026-02-22 22:08:30 +05:30
MochaMind
ddc3842eb7 fix: Indonesian translations 2026-02-22 22:08:26 +05:30
MochaMind
ad7a52e2cb fix: Portuguese, Brazilian translations 2026-02-22 22:08:23 +05:30
MochaMind
589f1246cb fix: Vietnamese translations 2026-02-22 22:08:20 +05:30
MochaMind
cbb3bf6f6a fix: Chinese Simplified translations 2026-02-22 22:08:17 +05:30
MochaMind
3ce05fc0dc fix: Turkish translations 2026-02-22 22:08:13 +05:30
MochaMind
7388b051ed fix: Swedish translations 2026-02-22 22:08:10 +05:30
MochaMind
e4c4f1a0c2 fix: Serbian (Cyrillic) translations 2026-02-22 22:08:06 +05:30
MochaMind
d4f2ea3358 fix: Slovenian translations 2026-02-22 22:08:03 +05:30
MochaMind
d490b2a3c6 fix: Russian translations 2026-02-22 22:08:00 +05:30
MochaMind
8915df90ae fix: Portuguese translations 2026-02-22 22:07:57 +05:30
MochaMind
0ac09df5d9 fix: Polish translations 2026-02-22 22:07:53 +05:30
MochaMind
0f0f8bd64b fix: Dutch translations 2026-02-22 22:07:50 +05:30
MochaMind
3eb0d5fd73 fix: Italian translations 2026-02-22 22:07:46 +05:30
MochaMind
6f77a11522 fix: Hungarian translations 2026-02-22 22:07:43 +05:30
MochaMind
3f2603a34d fix: German translations 2026-02-22 22:07:40 +05:30
MochaMind
48fc867b7b fix: Danish translations 2026-02-22 22:07:37 +05:30
MochaMind
4674b5a207 fix: Czech translations 2026-02-22 22:07:34 +05:30
MochaMind
c2b8b8469d fix: Arabic translations 2026-02-22 22:07:31 +05:30
MochaMind
50daec2972 fix: Spanish translations 2026-02-22 22:07:28 +05:30
MochaMind
63defa40cb chore: update POT file (#52857) 2026-02-22 13:33:52 +01:00
MochaMind
f7830fddeb fix: Vietnamese translations 2026-02-21 21:47:16 +05:30
MochaMind
f65d50b7e4 fix: Swedish translations 2026-02-21 21:47:09 +05:30
MochaMind
dcd47223fa fix: Dutch translations 2026-02-21 21:46:52 +05:30
Diptanil Saha
d6531189f9 Merge pull request #52842 from diptanilsaha/ref-fy
refactor: `Fiscal Year` cleanup
2026-02-21 12:39:14 +05:30
Mihir Kandoi
b49adbd74c Merge pull request #52845 from mihir-kandoi/remove-supplier-invoice-date-validdation 2026-02-21 12:24:48 +05:30
diptanilsaha
4c76786ce4 fix(fiscal_year): Fiscal Year auto-generation and notification 2026-02-21 12:17:09 +05:30
Mihir Kandoi
7cff0ba626 fix: remove supplier invoice date/posting date validation 2026-02-21 12:08:37 +05:30
diptanilsaha
94fb7e11b4 fix(fiscal_year_company): made company field mandatory 2026-02-21 02:02:03 +05:30
diptanilsaha
74ac28fc70 refactor: Fiscal Year DocType cleanup 2026-02-21 01:58:45 +05:30
Shllokkk
d0323dea65 fix: add missing logic to update requested qty on cancel of a material request 2026-02-20 20:29:43 +05:30
Shllokkk
519e115eb2 Merge branch 'develop' into so-mr-po 2026-02-20 20:11:44 +05:30
Shllokkk
1a4d7ad937 fix: correct ordered_qty and requested_qty for Sales Order Item through a patch 2026-02-20 20:09:18 +05:30
Shllokkk
c2f19036f3 fix: correct fields being updated on material request and purchase order creation from sales order 2026-02-20 19:58:23 +05:30
Mihir Kandoi
969f01fca3 Merge pull request #52835 from mihir-kandoi/inconsistent-label-name 2026-02-20 16:58:15 +05:30
Mihir Kandoi
d6e1ca0f10 fix: inconsistent label name between parent and child 2026-02-20 16:40:32 +05:30
Mihir Kandoi
32d09acba9 Merge pull request #52825 from mihir-kandoi/gh52746 2026-02-20 14:23:40 +05:30
Mihir Kandoi
59492cf08e Merge pull request #52821 from mihir-kandoi/gh52770 2026-02-20 14:23:16 +05:30
ruthra kumar
9902473321 Merge pull request #52783 from frappe/l10n_develop
fix: sync translations from crowdin
2026-02-20 14:22:57 +05:30
Mihir Kandoi
ba96d37c11 fix: update items fetches wrong item code 2026-02-20 14:21:26 +05:30
Mihir Kandoi
1352dc79bb fix: sensible insufficient stock message in pick list 2026-02-20 10:29:07 +05:30
Mihir Kandoi
548cdb55d7 Merge pull request #52814 from Shllokkk/selling-type-hints
refactor(selling): add type hints to whitelisted function parameters
2026-02-19 23:07:49 +05:30
Mihir Kandoi
b2afda0462 Merge pull request #52781 from Shllokkk/buying-type-hints
refactor(buying): add type hints for whitelisted function parameters
2026-02-19 23:05:49 +05:30
Mihir Kandoi
866bca70f9 Merge pull request #52811 from nishkagosalia/gh-52373
fix: permission issue for quotation item during update item
2026-02-19 22:57:49 +05:30
Shllokkk
d3911cd7d8 fix: add missing type hints due to failing test case 2026-02-19 22:08:18 +05:30
Raffael Meyer
387fb1b202 feat: add Bank Transaction as Reference Type to Journal Entry Account (#52760)
* feat: add Bank Transaction as Reference Type to Journal Entry Account

* fix: take care of existing property setters

* fix: cancelling Bank Transactions should still be possible

* fix: handle blank options in patch

* fix: hide Reference Due Date for Bank Transaction
2026-02-19 16:20:12 +00:00
MochaMind
0ef15e6364 fix: Serbian (Latin) translations 2026-02-19 21:32:58 +05:30
MochaMind
6e95eebfa4 fix: Thai translations 2026-02-19 21:32:39 +05:30
MochaMind
a4175553fc fix: Swedish translations 2026-02-19 21:32:04 +05:30
MochaMind
b8d7bb2c08 fix: Serbian (Cyrillic) translations 2026-02-19 21:31:59 +05:30
Shllokkk
2504f0fc0c refactor(selling): add type hints to whitelisted function parameters 2026-02-19 20:39:05 +05:30
Mihir Kandoi
732c98b72f fix: typo 2026-02-19 20:28:33 +05:30
Mihir Kandoi
6342e9a3e2 fix: ignore permissions instead of saving parent 2026-02-19 20:27:54 +05:30
ervishnucs
8fe0bf4ba3 fix: check gl account of an associated bank account in bank transaction 2026-02-19 18:39:54 +05:30
Nishka Gosalia
58b8af0fa8 fix: permission issue for quotation item during update item 2026-02-19 17:16:06 +05:30
Krishna Shirsath
870254b710 refactor(employee): reorganize joining and employee exit tabs at the end. 2026-02-19 17:14:50 +05:30
Krishna Shirsath
6513185cb7 refactor(employee): create user function -removed useless function calls 2026-02-19 17:00:35 +05:30
Krishna Shirsath
57f3048d27 feat(employee): Add automatic user creation feature and related validations. Create User on Import. 2026-02-19 15:51:53 +05:30
diptanilsaha
fa5eae08a0 fix(test_purchase_order): validation to create pi from po with terms
Fixed the test to check for error while creating Purchase Invoice from unsubmitted Purchase Order
2026-02-19 14:44:15 +05:30
Mihir Kandoi
4561e3a389 Merge pull request #52804 from marcramser/patch-6
fix(Purchase Receipt): copy project from first row when adding items
2026-02-19 14:01:39 +05:30
Marc Ramser
21423676c9 fix(Purchase Receipt): copy project from first row when adding items
Adds `items_add` method to copy expense_account, cost_center and project from first row to newly added items, matching Purchase Invoice behavior.
2026-02-19 09:25:38 +01:00
Shllokkk
531112e364 refactor(buying): correct broken test case test_make_purchase_invoice_with_terms 2026-02-19 13:22:33 +05:30
Sudharsanan11
4d40c84a31 fix(manufacturing): update status for work order before calculating planned qty 2026-02-19 12:59:11 +05:30
Mihir Kandoi
fc54565d95 Merge pull request #52792 from mihir-kandoi/gh52782
fix: unable to submit subcontracting order if created from material r…
2026-02-19 11:09:17 +05:30
Mihir Kandoi
37323480dd fix: unable to submit subcontracting order if created from material request 2026-02-19 10:50:26 +05:30
Mihir Kandoi
29bc300282 Merge pull request #52794 from mihir-kandoi/sre-read-only
fix: reservation based on field should be read only in SRE
2026-02-19 10:39:36 +05:30
Mihir Kandoi
21452b4c6e fix: reservation based on field should be read only in SRE 2026-02-19 10:18:26 +05:30
Mihir Kandoi
c98e1454fc Merge pull request #52490 from thomasantony12/patch-1
fix: Add handling for Sales Invoice Item quantity field
2026-02-19 09:42:12 +05:30
Mihir Kandoi
1fc2eddf6f fix: add purchase invoice as well 2026-02-19 04:45:20 +05:30
Mihir Kandoi
024c268fe0 Merge pull request #52712 from mihir-kandoi/gh52710
fix: addresses portal
2026-02-19 04:39:35 +05:30
Shllokkk
85c8296548 fix: add missing type hints 2026-02-18 22:37:25 +05:30
MochaMind
b15716f65a fix: Persian translations 2026-02-18 20:27:11 +05:30
MochaMind
a6f5130db1 fix: Portuguese, Brazilian translations 2026-02-18 20:27:07 +05:30
MochaMind
37b9402214 fix: Swedish translations 2026-02-18 20:27:00 +05:30
ruthra kumar
390294a92b Merge pull request #52780 from ruthra-kumar/journal_entry_form_cleanup
refactor: Journal Entry form cleanup
2026-02-18 18:00:46 +05:30
Shllokkk
a6bb44b421 refactor(buying): add type hints for whitelisted function parameters 2026-02-18 17:50:19 +05:30
Khushi Rawat
d3ebcd85b4 Merge pull request #52776 from khushi8112/type-hint-for-projects-module
refactor(projects): add type hints to functions
2026-02-18 17:33:31 +05:30
ruthra kumar
73d5aa35a1 refactor: Journal Entry form cleanup 2026-02-18 17:26:19 +05:30
khushi8112
39d3f4580e refactor(projects): add type hints to functions 2026-02-18 17:13:39 +05:30
mergify[bot]
40391252d5 Merge branch 'develop' into fixing-emp-contacts 2026-02-18 10:47:34 +00:00
Mihir Kandoi
1f62cecdfd Merge pull request #52628 from aerele/fix/support-#59774
fix(manufacturing): set pick list purpose while creating it from work order
2026-02-18 15:14:50 +05:30
Mihir Kandoi
e5f138d004 Merge pull request #52771 from mihir-kandoi/type-hints-manufacturing 2026-02-18 14:16:57 +05:30
Sudharsanan11
23ccc2a8c5 fix(manufacturing): set pick list purpose while creating it from work order 2026-02-18 14:05:42 +05:30
Mihir Kandoi
fad53f6fe4 refactor(manufacturing): add args type hints to functions 2026-02-18 13:58:44 +05:30
Khushi Rawat
7f9066c05a Merge pull request #52734 from khushi8112/type-hint-for-asset-module
refactor(assets): add type annotations in asset module
2026-02-18 12:59:44 +05:30
khushi8112
eb17284711 fix: args must be wrapped under a ctx 2026-02-18 12:35:29 +05:30
MochaMind
e159fc2779 fix: sync translations from crowdin (#52748)
* fix: Portuguese, Brazilian translations

* fix: Persian translations
2026-02-18 12:30:22 +05:30
ruthra kumar
747595082d Merge pull request #52763 from ruthra-kumar/fix_payment_request_bug
fix: better permissions on make payment request
2026-02-18 12:28:06 +05:30
khushi8112
beffc016da refactor: Optimize asset depreciation schedule retrieval with type hints 2026-02-18 12:22:55 +05:30
Mihir Kandoi
f9ec45eda7 Merge pull request #52762 from mihir-kandoi/type-hints-subcontracting
refactor(subcontracting): add arg type hints to functions
2026-02-18 12:17:24 +05:30
Mihir Kandoi
1bc0b40a2e refactor(subcontracting): add arg type hints to functions 2026-02-18 12:01:24 +05:30
ruthra kumar
f36962fc58 fix: better permissions on make payment request 2026-02-18 11:54:53 +05:30
khushi8112
36dcf8b2a5 fix: type hint in make depreciation entry function 2026-02-18 11:33:12 +05:30
Mihir Kandoi
e317ab1479 fix: addresses portal 2026-02-18 11:15:32 +05:30
Diptanil Saha
ea04f55d12 Merge pull request #52759 from diptanilsaha/utils_ta
refactor(utilities): type annotations for whitelisted methods
2026-02-18 01:56:33 +05:30
diptanilsaha
cac6b65dee refactor(utilities): type annotations for whitelisted methods 2026-02-18 01:39:50 +05:30
Diptanil Saha
c6a292f6a9 fix: user permission on reports (#52709) 2026-02-17 21:56:25 +05:30
Mihir Kandoi
e6825476e7 Merge pull request #52750 from mihir-kandoi/st60110
fix: setup fails to set abbr to departments
2026-02-17 21:24:32 +05:30
Mihir Kandoi
54144f72a5 Merge pull request #52743 from Mutantpenguin/patch-3
bug: fix comparison regarding `None` values
2026-02-17 21:21:25 +05:30
Mihir Kandoi
debe868950 fix: setup fails to set abbr to departments 2026-02-17 21:07:30 +05:30
khushi8112
773cc80ed4 fix: incorrect type hint 2026-02-17 17:51:27 +05:30
Markus Lobedann
3fd5a0f100 fix: bug with comparison regarding None values and empty string
In their default state, the fields can be `None`. When a user enters something and deletes it afterwards, the fields contain an empty string.

This fixes the comparison.
2026-02-17 12:46:30 +01:00
rohitwaghchaure
83f5c03494 Merge pull request #52729 from rohitwaghchaure/feat-negative-batch-report
feat: Negative Batch report
2026-02-17 16:33:28 +05:30
Mihir Kandoi
eb0fa4d26d Merge pull request #52677 from mihir-kandoi/standalone-credit-debit-note
fix: standalone credit/debit notes should not fetch any serial or bat…
2026-02-17 16:09:47 +05:30
Mihir Kandoi
4f4bd98943 Merge pull request #52733 from mihir-kandoi/allow-sequence-id-edit
fix: allow sequence ID edit in BOM if routing is not set
2026-02-17 16:05:33 +05:30
Rohit Waghchaure
34edbed00b feat: Negative Batch report 2026-02-17 16:05:13 +05:30
khushi8112
84773a3a32 refactor(assets): add type annotations in asset module 2026-02-17 15:56:24 +05:30
Mihir Kandoi
08529964b4 fix: allow sequence id edit in BOM if routing is not set 2026-02-17 15:39:52 +05:30
ruthra kumar
2c011bb9ad Merge pull request #52679 from AarDG10/fix-acc-timeline
fix: log changes made to accounts settings
2026-02-17 13:55:24 +05:30
ruthra kumar
ee9ce0dee8 Merge pull request #52648 from Abdeali099/Abdeali/fix-typo
fix: correct typos in marketing campaign custom fields function
2026-02-17 13:41:40 +05:30
Krishna Shirsath
3b521b74ea feat(employee): Create User button and form. 2026-02-17 13:32:09 +05:30
ljain112
1d3d09f48c refactor: use postprocess in mapped_doc to update items in subcontracting controller 2026-02-17 13:16:41 +05:30
ruthra kumar
e8ef6f1afb Merge pull request #52654 from Jarif-Junaeed/develop
fix: resolve POS crash and correct is_return typo in TransactionBase
2026-02-17 13:10:29 +05:30
Mihir Kandoi
a374d0b3e7 Merge pull request #52720 from Shllokkk/pricing-rule-fix
fix: wrong display_depends_on condition for item group and brand chil…
2026-02-17 12:59:22 +05:30
Shllokkk
de2843d9f1 fix: wrong display_depends_on condition for item group and brand child tables 2026-02-17 12:28:15 +05:30
Vishnu Priya Baskaran
ae0be7f6ce Merge pull request #52649 from aerele/fix-sales-funnel-layout
fix: ensure layout has Bootstrap row and column
2026-02-17 12:25:15 +05:30
Mihir Kandoi
7860673855 Merge pull request #52716 from mihir-kandoi/gh52551
fix: do not allow plant floor company and warehouse to be updated
2026-02-17 12:11:53 +05:30
Mihir Kandoi
fd72132743 fix: do not allow plant floor company and warehouse to be updated 2026-02-17 11:54:12 +05:30
Mihir Kandoi
1eb9f5b803 Merge pull request #52713 from mihir-kandoi/st60127
fix: production plan status
2026-02-17 11:23:17 +05:30
Mihir Kandoi
b3e6b304e4 fix: production plan status 2026-02-17 10:43:47 +05:30
Diptanil Saha
c8c54a42e2 Merge pull request #52706 from diptanilsaha/wsb-pp
fix(selling-workspace-sidebar): changed order of pos profile
2026-02-17 00:40:33 +05:30
diptanilsaha
72f4fd08ee fix(selling-workspace-sidebar): changed order of pos profile 2026-02-17 00:23:01 +05:30
rohitwaghchaure
4defb104db Merge pull request #52620 from Shllokkk/production-plan-fix
fix: prevent rows from being added to sub_assembly_items and mr_items
2026-02-17 00:11:31 +05:30
Mihir Kandoi
48cafc8e57 Merge pull request #52626 from aerele/fix/support-#59888
fix(manufacturing): add sales order fields in subassembly child table
2026-02-16 23:41:16 +05:30
rohitwaghchaure
8adf8aed41 Merge pull request #52699 from rohitwaghchaure/fixed-negative-stock-validation-sle
fix: consider sle for negative stock validation
2026-02-16 23:25:52 +05:30
Sudharsanan11
341dc4be7a test(manufacturing): add test to validate the sales order references for sub assembly items 2026-02-16 23:21:35 +05:30
Sudharsanan11
0f2ed28ab7 fix(manufacturing): set sales order references in subassembly child table 2026-02-16 23:21:35 +05:30
Sudharsanan11
c2282eaf08 fix(manufacturing): add sales order fields in subassembly child table 2026-02-16 23:21:35 +05:30
Rohit Waghchaure
38f35acffe fix: consider sle for negative stock validation 2026-02-16 23:02:15 +05:30
Shllokkk
25f979a825 fix: prevent rows from being added to sub_assembly_items and mr_items 2026-02-16 22:33:36 +05:30
rohitwaghchaure
afa14e6cd4 Merge pull request #52678 from mihir-kandoi/sales-purchase-invoice-update-stock-cleanup
chore: do not show stock details if update stock is disabled
2026-02-16 22:31:25 +05:30
Diptanil Saha
802507d321 Merge pull request #52692 from frappe/l10n_develop
fix: sync translations from crowdin
2026-02-16 21:16:11 +05:30
rohitwaghchaure
12ff8e6279 Merge pull request #52691 from rohitwaghchaure/fixed-cancel-sabb-for-lcv
fix: cancel SABB if SLE cancelled from LCV
2026-02-16 21:11:38 +05:30
Soham Kulkarni
219cf6bc57 fix(pos_invoice): add correct depends on condition (#52689)
* fix(pos_invoice): add correct depends on condition

* fix: show field in sales order

* refactor: eval condition
2026-02-16 15:36:30 +00:00
MochaMind
a7ec72b25c fix: Esperanto translations 2026-02-16 20:22:05 +05:30
MochaMind
4bd3d4d36c fix: Serbian (Latin) translations 2026-02-16 20:22:01 +05:30
MochaMind
d425270c88 fix: Norwegian Bokmal translations 2026-02-16 20:21:57 +05:30
MochaMind
f8c8c1cc2d fix: Bosnian translations 2026-02-16 20:21:53 +05:30
MochaMind
cc02c411b6 fix: Burmese translations 2026-02-16 20:21:49 +05:30
MochaMind
65dd7d4851 fix: Croatian translations 2026-02-16 20:21:45 +05:30
MochaMind
47b80cbc2b fix: Thai translations 2026-02-16 20:21:39 +05:30
MochaMind
20d2317f61 fix: Persian translations 2026-02-16 20:21:35 +05:30
MochaMind
d2a4b8bfd8 fix: Indonesian translations 2026-02-16 20:21:30 +05:30
MochaMind
2b26d859c3 fix: Portuguese, Brazilian translations 2026-02-16 20:21:26 +05:30
MochaMind
3516cda350 fix: Vietnamese translations 2026-02-16 20:21:22 +05:30
MochaMind
4d51d0d351 fix: Chinese Simplified translations 2026-02-16 20:21:19 +05:30
MochaMind
303b7a117c fix: Turkish translations 2026-02-16 20:21:15 +05:30
MochaMind
83006a3ed1 fix: Swedish translations 2026-02-16 20:21:11 +05:30
MochaMind
b3e9fa2142 fix: Serbian (Cyrillic) translations 2026-02-16 20:21:07 +05:30
MochaMind
45d7a398b6 fix: Slovenian translations 2026-02-16 20:21:01 +05:30
MochaMind
ffb88c30b9 fix: Russian translations 2026-02-16 20:20:58 +05:30
MochaMind
40fd9a8008 fix: Portuguese translations 2026-02-16 20:20:53 +05:30
MochaMind
eb357a2805 fix: Polish translations 2026-02-16 20:20:48 +05:30
MochaMind
fd2cadd431 fix: Dutch translations 2026-02-16 20:20:44 +05:30
MochaMind
e66bbe4eec fix: Italian translations 2026-02-16 20:20:40 +05:30
Rohit Waghchaure
f23a49a25e fix: cancel SABB if SLE cancelled from LCV 2026-02-16 20:20:39 +05:30
MochaMind
1c749c01cd fix: Hungarian translations 2026-02-16 20:20:37 +05:30
MochaMind
f95ff68c10 fix: German translations 2026-02-16 20:20:30 +05:30
MochaMind
5815a92273 fix: Danish translations 2026-02-16 20:20:26 +05:30
MochaMind
bb8d84e65e fix: Czech translations 2026-02-16 20:20:23 +05:30
MochaMind
c28020a1bd fix: Arabic translations 2026-02-16 20:20:19 +05:30
MochaMind
95b0ec70cb fix: Spanish translations 2026-02-16 20:20:15 +05:30
MochaMind
1c0f2e1fba fix: French translations 2026-02-16 20:20:11 +05:30
Khushi Rawat
83338675f9 Merge pull request #52331 from aerele/fix-validate-asset-category-accounts
fix(asset): validate depreciation when category has active depreciable assets
2026-02-16 17:45:18 +05:30
Khushi Rawat
a6f808702f Merge pull request #52688 from khushi8112/toggle-reference-doc
fix: toggle reference doc on asset_type change
2026-02-16 17:38:39 +05:30
khushi8112
8e71060931 fix: toggle reference doc on asset_type change 2026-02-16 17:28:51 +05:30
rohitwaghchaure
659112842e Merge pull request #52681 from rohitwaghchaure/fixed-better-validation-message-for-batch
fix: better validation for negative batch
2026-02-16 15:15:54 +05:30
Rohit Waghchaure
a8636e4f59 fix: better validation for negative batch 2026-02-16 13:06:11 +05:30
AarDG10
45febbabd7 fix: log changes made to accounts settings 2026-02-15 23:18:49 +05:30
Mihir Kandoi
cdc62e7327 chore: do not show serial batch selector if not needed 2026-02-15 21:09:04 +05:30
Mihir Kandoi
4499e974a0 chore: do not show stock details if update stock is disabled 2026-02-15 20:54:44 +05:30
Mihir Kandoi
2017edca88 fix: standalone credit/debit notes should not fetch any serial or batch by default 2026-02-15 20:29:57 +05:30
MochaMind
894771029c chore: update POT file (#52673) 2026-02-15 14:00:17 +01:00
Mihir Kandoi
bdf909e94e Merge pull request #52670 from mihir-kandoi/st59534
fix: total weight does not update when updating items
2026-02-15 14:14:11 +05:30
Mihir Kandoi
63323a2611 fix: total weight does not update when updating items 2026-02-15 13:58:32 +05:30
Mihir Kandoi
36393e447e Merge pull request #52658 from aerele/fix-filter-for-update-items
fix: allow non-stock items while updating items
2026-02-15 12:57:39 +05:30
Diptanil Saha
fe01206a73 Merge pull request #52666 from frappe/l10n_develop
fix: sync translations from crowdin
2026-02-15 11:29:26 +05:30
MochaMind
e766bb0cd7 fix: Burmese translations 2026-02-14 19:11:32 +05:30
MochaMind
53e3222935 fix: Persian translations 2026-02-14 19:11:29 +05:30
Jarif Junaeed
47353fbceb fix: resolve POS crash and correct is_return typo in TransactionBase 2026-02-14 14:08:57 +06:00
ruthra kumar
a23c2f2166 Merge pull request #52643 from aerele/quotation-lost-reason-detail
fix: consider table multiselect in delete transaction
2026-02-13 14:07:07 +05:30
ervishnucs
07db5941aa fix: allow non-stock items while updating items 2026-02-13 11:58:54 +05:30
ruthra kumar
dd9e1814b0 Merge pull request #52651 from frappe/l10n_develop
fix: sync translations from crowdin
2026-02-13 10:14:33 +05:30
MochaMind
0e262de96f fix: Spanish translations 2026-02-12 19:09:18 +05:30
Abdeali Chharchhoda
6b7fed7f59 fix: correct typos in marketing campaign custom fields function 2026-02-12 15:41:14 +05:30
ruthra kumar
7ba2ed6f3f Merge pull request #52644 from ruthra-kumar/use_query_builder_for_profitability_analysis
refactor: use query builder for profitability analysis
2026-02-12 14:24:04 +05:30
ruthra kumar
5e34325604 refactor: use query builder for profitability analysis 2026-02-12 14:08:27 +05:30
SowmyaArunachalam
9bb60405e7 fix: removed lost reason detail 2026-02-12 13:26:47 +05:30
SowmyaArunachalam
be3d2422a7 fix: consider table multiselect in delete transaction 2026-02-12 13:22:17 +05:30
Navin-S-R
bd9d9c3cc7 fix: validate depreciation accounts exist for all companies with depreciable assets 2026-02-12 13:08:19 +05:30
ruthra kumar
1218c0f518 Merge pull request #52640 from ruthra-kumar/use_query_builder_for_sales_person_commision
refactor: use query builder for sales person commission summary
2026-02-12 12:36:43 +05:30
ruthra kumar
7105e3fb69 refactor: use query builder for sales person commission summary 2026-02-12 12:19:51 +05:30
ruthra kumar
ae985c00e9 Merge pull request #52571 from belee-asp/fix/report-letterhead-hardcode
fix(stock): remove hardcoded letter_head from report
2026-02-12 11:48:58 +05:30
ruthra kumar
803c09a4ae Merge pull request #52619 from aerele/feat-show-formatted-currency-on-preview
feat: show formatted currency symbol on ledger preview
2026-02-12 11:28:21 +05:30
Navin-S-R
1b288682e8 chore: resolve conflicts 2026-02-12 11:13:46 +05:30
Navin-S-R
7d14833856 fix: handle duplicate acctount row for the same company 2026-02-12 11:13:46 +05:30
Navin-S-R
2c6054d51a fix(asset category): validate depreciation accounts when category has active depreciable assets 2026-02-12 11:13:46 +05:30
Navin-S-R
464e1929db fix(asset): validate depreciation accounts for depreciable asset upon creation 2026-02-12 11:13:46 +05:30
ruthra kumar
33d0e9559a Merge pull request #52507 from ruthra-kumar/toggle_for_utm_parameters
refactor: toggle for UTM parameters
2026-02-12 10:55:45 +05:30
ruthra kumar
20af546b26 refactor: better description on toggle 2026-02-12 10:38:50 +05:30
Diptanil Saha
7e99a1f4b0 Merge pull request #52625 from frappe/l10n_develop 2026-02-11 19:15:44 +05:30
MochaMind
2adde497c8 fix: Serbian (Latin) translations 2026-02-11 18:59:56 +05:30
MochaMind
97d6a35edc fix: Serbian (Cyrillic) translations 2026-02-11 18:59:32 +05:30
Navin-S-R
5c8cb1e7ec feat: show formatted currency symbol on ledger preview 2026-02-11 17:29:20 +05:30
ruthra kumar
d1217dfedd Merge pull request #52593 from ljain112/tds-report-columns
refactor: update labels for tax withholding reports columns to improve clarity
2026-02-10 18:12:09 +05:30
ruthra kumar
4ddb3c7a6c Merge pull request #52602 from frappe/l10n_develop2
fix: sync translations from crowdin
2026-02-10 18:11:22 +05:30
ruthra kumar
3ed7627ec3 Merge pull request #52017 from aerele/fix-gross-profit-return-period
fix(gross-profit): handle returns outside the given sale period
2026-02-10 18:10:30 +05:30
MochaMind
1b5d5c5be8 fix: Swedish translations 2026-02-10 17:54:26 +05:30
MochaMind
4946a13d45 fix: Italian translations 2026-02-10 17:54:05 +05:30
ruthra kumar
578783af8e refactor: utm analytics section delivery note 2026-02-10 14:36:55 +05:30
ruthra kumar
223d560b32 refactor: utm section in opportunity 2026-02-10 14:36:55 +05:30
ruthra kumar
71e8941285 refactor: utm analytics section in lead 2026-02-10 14:36:52 +05:30
ruthra kumar
7de0ca164d refactor: utm analytics section in pos profile 2026-02-10 14:24:59 +05:30
ruthra kumar
0bafa347a5 refactor: utm section pos invoice 2026-02-10 14:23:28 +05:30
ljain112
2cfdcc1af4 refactor: update labels for tax withholding reports columns to improve clarity 2026-02-10 12:54:46 +05:30
ruthra kumar
678d261f40 refactor: utm section in sales invoice 2026-02-10 11:59:17 +05:30
ruthra kumar
c00bf7df6a refactor: utm section in sales order 2026-02-10 11:56:32 +05:30
Navin-S-R
047b278791 fix(gross-profit): handle item group filters 2026-02-10 11:47:35 +05:30
ruthra kumar
aaf5f923b0 refactor: hide UTM parameters 2026-02-10 11:40:12 +05:30
Navin-S-R
fdfa7bc963 test: fix test assertions to use index-based totals 2026-02-10 11:29:58 +05:30
Navin-S-R
3ab978ab46 test: validate sales person wise gross profit 2026-02-10 11:29:58 +05:30
Navin-S-R
4da3d43013 test: validate return invoice profit and profit percentage 2026-02-10 11:29:58 +05:30
Navin-S-R
51709f032f fix: handle gross profit and percentage for return invoices 2026-02-10 11:29:58 +05:30
Navin-S-R
67d8223f73 fix(gross-profit): handle returns outside sale period 2026-02-10 11:29:57 +05:30
El-Shafei H.
da07f84e44 fix: Added a missing option to the currency field (#52528) 2026-02-10 02:14:06 +05:30
V Shankar
23a73c9cdb fix(map_current_doc): prevent mutation of query args in get_query (#52202) 2026-02-10 00:16:59 +05:30
Mihir Kandoi
7b197cbe1c Merge pull request #52527 from aerele/fix/support-59242
fix(stock): correct warehouse mapping for material issue
2026-02-09 21:05:56 +05:30
Roxxane
99cd29d88f fix(stock): remove hardcoded letter_head from report
The 'Incorrect Serial and Batch Bundle' report had a hardcoded
letter_head value of 'Test', preventing users from deleting a
Letter Head named 'Test' due to link check.

Standard reports should not reference specific Letter Head names.

Fixes #52569
2026-02-09 22:25:11 +08:00
Akhil Narang
0cf4c2ded5 Merge pull request #52559 from akhilnarang/drop-db-query-usage
refactor: drop usages of db_query
2026-02-09 18:52:04 +05:30
Diptanil Saha
271a982cb4 Merge pull request #52562 from frappe/l10n_develop2
fix: sync translations from crowdin
2026-02-09 17:49:10 +05:30
Pandiyan37
f22b9e297b fix(stock): inward stock for pick list test record 2026-02-09 17:45:50 +05:30
MochaMind
61d0863239 fix: Esperanto translations 2026-02-09 17:30:45 +05:30
MochaMind
803cc7c653 fix: Serbian (Latin) translations 2026-02-09 17:30:37 +05:30
MochaMind
b4341df44c fix: Norwegian Bokmal translations 2026-02-09 17:30:31 +05:30
MochaMind
4a77a8b840 fix: Bosnian translations 2026-02-09 17:30:27 +05:30
MochaMind
680117e97e fix: Burmese translations 2026-02-09 17:30:22 +05:30
MochaMind
4bab80faaa fix: Croatian translations 2026-02-09 17:30:11 +05:30
MochaMind
74a1707e3f fix: Thai translations 2026-02-09 17:30:01 +05:30
MochaMind
f145331ae5 fix: Persian translations 2026-02-09 17:29:57 +05:30
MochaMind
be5df6b2b7 fix: Indonesian translations 2026-02-09 17:29:53 +05:30
MochaMind
14a5ff2b54 fix: Portuguese, Brazilian translations 2026-02-09 17:29:48 +05:30
MochaMind
f585ddea71 fix: Vietnamese translations 2026-02-09 17:29:44 +05:30
MochaMind
958ba61f66 fix: Chinese Simplified translations 2026-02-09 17:29:40 +05:30
MochaMind
f7b18159a6 fix: Turkish translations 2026-02-09 17:29:37 +05:30
MochaMind
ae759481e1 fix: Swedish translations 2026-02-09 17:29:33 +05:30
MochaMind
c2bafcf83b fix: Serbian (Cyrillic) translations 2026-02-09 17:29:28 +05:30
MochaMind
04b5e1b3a0 fix: Slovenian translations 2026-02-09 17:29:24 +05:30
MochaMind
3207a9e642 fix: Russian translations 2026-02-09 17:29:19 +05:30
MochaMind
67e8286a57 fix: Portuguese translations 2026-02-09 17:29:14 +05:30
MochaMind
7c0c41f3de fix: Polish translations 2026-02-09 17:29:09 +05:30
MochaMind
c5da02dbd0 fix: Dutch translations 2026-02-09 17:29:05 +05:30
MochaMind
ee1ae88ecd fix: Italian translations 2026-02-09 17:29:01 +05:30
MochaMind
e7b8c257a3 fix: Hungarian translations 2026-02-09 17:28:57 +05:30
MochaMind
93078bcf0c fix: German translations 2026-02-09 17:28:54 +05:30
MochaMind
566072a17f fix: Danish translations 2026-02-09 17:28:50 +05:30
MochaMind
31191096ad fix: Czech translations 2026-02-09 17:28:46 +05:30
MochaMind
0975222e3a fix: Arabic translations 2026-02-09 17:28:42 +05:30
MochaMind
576279fa92 fix: Spanish translations 2026-02-09 17:28:38 +05:30
MochaMind
21eec60924 fix: French translations 2026-02-09 17:28:34 +05:30
Poojashree T R
e98b68c38f fix: validate asset movement transaction date (#52340)
* fix: validate asset transaction date

* fix: validate asset transaction date

* fix: add translation in validate_transaction_date

* test: test_movement_transaction_date

* fix: to ensure test reliability
2026-02-09 17:20:42 +05:30
Poojashree T R
155f6afb48 fix: set asset status as fully depreciated (#52309)
* fix: set asset status as fully depreciated

* test: test_is_fully_depreciated_asset_status

* fix: remove unused condition
2026-02-09 17:01:40 +05:30
Akhil Narang
1e45195ef9 refactor: drop usages of db_query
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2026-02-09 16:53:02 +05:30
rohitwaghchaure
3a2e4ff5e5 Merge pull request #52550 from rohitwaghchaure/feat-allow-negative-stock-for-batch
feat: allow negative stock for the batch item
2026-02-09 16:19:29 +05:30
Mihir Kandoi
b2a0d6559f Merge pull request #52501 from aerele/quotation-ordered-status-regression
fix(quotation): ignore zero ordered_qty
2026-02-09 16:08:22 +05:30
Pratik Badhe
22123dd955 fix: email campaign timeout issue (#51994)
* fix: email campaign timeout issue

* refactor: email campaign backend logic

* refactor: use sendmail instead of manually batching
2026-02-09 16:07:46 +05:30
Rohit Waghchaure
376ab0e346 feat: allow negative stock for the batch item 2026-02-09 15:04:19 +05:30
Pandiyan37
da0322e994 test(stock): add test to check from warehouse for issue type 2026-02-09 13:06:27 +05:30
rohitwaghchaure
479b44c610 Merge pull request #52516 from aerele/fix/support-#59057
fix(stock): ignore pos reserved batches for stock levels
2026-02-09 11:57:43 +05:30
Pandiyan37
a34e8c99cd fix(stock): set source warehouse for issue type 2026-02-09 10:58:50 +05:30
Mihir Kandoi
b8e1758dcf Merge pull request #52538 from mihir-kandoi/revert-gh52220
revert: "fix: allow sales invoice to be renamed"
2026-02-09 10:29:47 +05:30
Mihir Kandoi
2660907ac8 revert: "fix: allow sales invoice to be renamed"
This reverts commit 95fdbe55f9.
2026-02-09 10:13:23 +05:30
rohitwaghchaure
9f8c08fd18 Merge pull request #52534 from rohitwaghchaure/form-cleanup-supplier-form
refactor: supplier form cleanup
2026-02-09 09:36:32 +05:30
Sudharsanan11
47ac67f7a2 test(stock): add test to ignore pos reserved batches for stock levels 2026-02-08 23:28:06 +05:30
Jannat Patel
0cf9915698 fix: sync translations from crowdin (#52449)
* fix: French translations

* fix: Spanish translations

* fix: Arabic translations

* fix: Czech translations

* fix: Danish translations

* fix: German translations

* fix: Hungarian translations

* fix: Italian translations

* fix: Dutch translations

* fix: Polish translations

* fix: Portuguese translations

* fix: Russian translations

* fix: Slovenian translations

* fix: Serbian (Cyrillic) translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Vietnamese translations

* fix: Portuguese, Brazilian translations

* fix: Indonesian translations

* fix: Persian translations

* fix: Thai translations

* fix: Croatian translations

* fix: Burmese translations

* fix: Bosnian translations

* fix: Norwegian Bokmal translations

* fix: Serbian (Latin) translations

* fix: Esperanto translations

* fix: Serbian (Cyrillic) translations

* fix: Swedish translations

* fix: Croatian translations

* fix: Bosnian translations

* fix: Serbian (Latin) translations

* fix: Persian translations

* fix: Italian translations
2026-02-08 17:50:31 +01:00
MochaMind
dd1027f165 chore: update POT file (#52531) 2026-02-08 17:49:49 +01:00
Rohit Waghchaure
bd521d9089 refactor: supplier form cleanup 2026-02-08 20:37:57 +05:30
rohitwaghchaure
ebce5d2635 Merge pull request #52508 from rohitwaghchaure/form-cleanup-stock-manufacturing
refactor: form cleanup for stock and manufacturing doctypes
2026-02-08 20:21:46 +05:30
Sudharsanan11
277ba9cb79 fix(stock): ignore pos reserved batches for stock levels 2026-02-08 10:05:07 +05:30
Mihir Kandoi
86389b46e2 Merge pull request #52497 from aerele/fix/support-#59190
fix: add is_group filter for supplier_group and warehouse fields
2026-02-07 21:40:41 +05:30
Rohit Waghchaure
bf20ecca60 refactor: form cleanup for stock and manufacturing doctypes 2026-02-06 19:27:35 +05:30
ruthra kumar
8c960620cc Merge pull request #52504 from ruthra-kumar/ux_refactor_sales_order
refactor: form cleanup for sales order
2026-02-06 17:27:07 +05:30
Khushi Rawat
7be1c885cd Merge pull request #52393 from khushi8112/form-cleanup-assets
refactor: assets module form cleanup
2026-02-06 17:23:06 +05:30
ruthra kumar
3957df6511 Merge pull request #52495 from ruthra-kumar/ux_refactor_for_quotation
refactor: form cleanup for quotation
2026-02-06 17:13:19 +05:30
Sudharsanan11
a9829f5f7b fix(stock): add is group filter for warehouse fields 2026-02-06 17:05:24 +05:30
ruthra kumar
9db3fa1b65 refactor: form cleanup for quotation
1. use standard names for dimension section
2026-02-06 16:48:11 +05:30
ruthra kumar
93d1716eb5 refactor: form cleanup for sales order 2026-02-06 16:24:53 +05:30
khushi8112
4f59d580c4 chore: linters check 2026-02-06 16:22:44 +05:30
khushi8112
d1b81b96a5 fix: implement coderabbit suggested changes 2026-02-06 16:19:40 +05:30
khushi8112
9f7bfc0e36 refactor: asset repair UI changes 2026-02-06 16:19:40 +05:30
khushi8112
eb7932ed73 refactor: asset capitalization form cleanup 2026-02-06 16:19:40 +05:30
khushi8112
e8e8d233ab fix: test cases fixes related to new select box change 2026-02-06 16:19:40 +05:30
khushi8112
f2f509234b fix: test case 2026-02-06 16:19:40 +05:30
khushi8112
e90a3b5a56 fix(UI): improve asset action buttons group 2026-02-06 16:19:40 +05:30
khushi8112
c36fa5bdb6 fix: patch to migrate checkbox data into select 2026-02-06 16:19:40 +05:30
khushi8112
f7b9221324 refactor: use selectbox instead of checkboxes for asset type 2026-02-06 16:19:40 +05:30
khushi8112
4e7794bfc3 fix(UI): reposition fields for better UX 2026-02-06 16:19:40 +05:30
Mihir Kandoi
71ad323cb6 Merge pull request #52499 from nishkagosalia/fix-test-case 2026-02-06 15:58:48 +05:30
ravibharathi656
32ea37035e fix(quotation): ignore zero ordered_qty 2026-02-06 15:50:01 +05:30
Nishka Gosalia
81dceceb4f fix: test case 2026-02-06 15:42:15 +05:30
rohitwaghchaure
d02a85df50 Merge pull request #52494 from rohitwaghchaure/fixed-remove-lable-currency
fix: remove currency label for rounding adjustment
2026-02-06 13:30:24 +05:30
Rohit Waghchaure
000af2bc96 fix: remove currency label for rounding adjustment 2026-02-06 13:29:23 +05:30
Sudharsanan11
cfdc554a19 fix(buying): add supplier group link filters in field level 2026-02-06 13:12:04 +05:30
Khushi Rawat
6e744bcaa7 Merge pull request #52491 from khushi8112/asset-field-toggle-issue
fix: apply composite asset logic only in draft
2026-02-06 13:04:31 +05:30
khushi8112
ee501e884a fix: apply composite asset logic only in draft 2026-02-06 12:58:42 +05:30
Thomas antony
edfcaee99b fix: Add handling for Sales Invoice Item quantity field
Add handling for Sales Invoice Item quantity field
2026-02-06 12:48:15 +05:30
Mihir Kandoi
31ec3af164 Merge pull request #52219 from nishkagosalia/st-56775
fix: enabling skip delivery option for order type maintenance
2026-02-06 12:09:28 +05:30
Mihir Kandoi
70053fea8e Merge pull request #52000 from nishkagosalia/gh-51010
fix: Added validation for quality inspection in job card
2026-02-06 12:07:41 +05:30
Mihir Kandoi
ac9232d888 Merge pull request #52475 from mihir-kandoi/gh52358-2
fix: do not show update stock flag unneccessarily
2026-02-06 11:57:11 +05:30
Nishka Gosalia
1a22e3cb61 fix: enabling skip delivery option for order type maintenance 2026-02-06 11:53:09 +05:30
Diptanil Saha
57d14f695f refactor: global defaults (#52481)
* fix(global_defaults): set disable rounded total on POS Profiles

* fix: removed re-setting fields on toggle functions

* fix: removed property setter for print_hide
2026-02-06 10:18:07 +05:30
rohitwaghchaure
1415472674 Merge pull request #52476 from rohitwaghchaure/fixed-stock-reservation-for-fg
fix: stock reservation created against job card
2026-02-06 04:34:59 +05:30
Rohit Waghchaure
dca2cfd009 fix: stock reservation created against job card 2026-02-05 22:06:54 +05:30
Mihir Kandoi
5fb5b7b30e fix: do not show update stock flag unneccessarily 2026-02-05 20:45:46 +05:30
rohitwaghchaure
a632d5f313 Merge pull request #52469 from rohitwaghchaure/form-cleanup-master-doctypes
refactor: form cleanup for master doctypes related to stock module
2026-02-05 19:46:18 +05:30
rohitwaghchaure
4136ac26c4 Merge pull request #52467 from rohitwaghchaure/fixed-github-52466
fix: operation status and bom validation
2026-02-05 19:45:39 +05:30
Diptanil Saha
a67b548f44 fix(pos_profile): allow_partial_payment field description (#52470)
fix(pos_profile): allow_partial_payment field description
2026-02-05 13:07:40 +00:00
Nikhil Kothari
ef44528ba5 feat(accounts): expand Journal Entry Template to support dimensions and party (#51621)
* feat(accounts): expand Journal Entry Template to support dimensions and party

* fix: do not update standard row values
2026-02-05 18:24:26 +05:30
Rohit Waghchaure
6c49d5dc7d refactor: form cleanup for master doctypes related to stock module 2026-02-05 18:17:37 +05:30
Rohit Waghchaure
95baf953a8 fix: operation status and bom validation 2026-02-05 18:14:35 +05:30
rohitwaghchaure
c86e316670 Merge pull request #52459 from rohitwaghchaure/fixed-stock-reco-balance-value
fix: stock balance report issue
2026-02-05 14:49:22 +05:30
Rohit Waghchaure
7e584dd84a fix: stock balance report issue 2026-02-05 14:32:48 +05:30
Jatin3128
cb4e6ad825 Merge pull request #52438 from Jatin3128/fix-chart-label
fix(balance sheet): removed the extra labels from the chart
2026-02-05 13:23:55 +05:30
Deepesh Garg
4584893542 Merge pull request #52453 from deepeshgarg007/erpnext_form_cleanups_v1
fix: Currency fields label (Sales Invoice)
2026-02-05 13:10:53 +05:30
Mihir Kandoi
6b0fe32180 Merge pull request #52452 from mihir-kandoi/gh52451
fix: process loss error incorrectly thrown even when semi FG BOM does…
2026-02-05 12:57:22 +05:30
Deepesh Garg
e08fe10f31 fix: Currency fields label 2026-02-05 12:54:48 +05:30
Mihir Kandoi
99ddc36c26 fix: process loss error incorrectly thrown even when semi FG BOM does not have any process loss 2026-02-05 12:42:12 +05:30
Smit Vora
26854d8d42 Merge pull request #52360 from vorasmit/fix-calc-balance
fix: correctly calculate running balances for financial report
2026-02-05 12:23:09 +05:30
Diptanil Saha
1c5e36017c feat(pos): allow warehouse change configuration (#52437) 2026-02-05 12:00:42 +05:30
Mihir Kandoi
611f7d07c2 Merge pull request #52445 from mihir-kandoi/gh52444
fix: item code is tuple with operation id
2026-02-05 11:57:55 +05:30
Mihir Kandoi
481deee4b2 fix: item code is tuple with operation id 2026-02-05 11:42:52 +05:30
Deepesh Garg
116ea657cd Merge pull request #52399 from deepeshgarg007/erpnext_form_cleanups
refactor: Better organizing of the fields in various doctypes
2026-02-05 11:27:39 +05:30
Mihir Kandoi
e9befff0cb Merge pull request #52416 from aerele/fix/inventory-dimension-attribute
fix(stock): update target field attribute
2026-02-05 10:23:31 +05:30
Deepesh Garg
1770d92ea6 Merge branch 'develop' into erpnext_form_cleanups 2026-02-05 09:50:22 +05:30
Jatin3128
a64b5f2c5d fix(balance sheet): removed the extra labels from the chart 2026-02-05 05:02:46 +05:30
Diptanil Saha
812d7de7f7 fix: pos profile form cleanup (#52436) 2026-02-05 04:21:14 +05:30
Pandiyan37
21d0ee8db1 test(stock): testcase for different inventory dimension 2026-02-04 22:33:39 +05:30
Pandiyan37
14818b8311 Merge branch 'develop' of https://github.com/frappe/erpnext into fix/inventory-dimension-attribute 2026-02-04 22:09:09 +05:30
Mihir Kandoi
c9dbd58e7f Merge pull request #52427 from archielister/develop 2026-02-04 20:02:33 +05:30
rohitwaghchaure
e63ff83f24 Merge pull request #52422 from rohitwaghchaure/fixed-not-able-to-complete-jc
fix: not able to complete job card
2026-02-04 18:42:56 +05:30
rohitwaghchaure
d3e269a3d3 Merge pull request #52392 from rohitwaghchaure/form-cleanup-buying
refactor: Cleanup buying module related doctypes
2026-02-04 18:13:00 +05:30
ruthra kumar
b33a805702 Merge pull request #51990 from ruthra-kumar/security_fixes
refactor: use https over http while saving website link
2026-02-04 18:01:55 +05:30
Deepesh Garg
efc4c900f3 chore: linting issues 2026-02-04 17:55:06 +05:30
archielister
e4df0a393a fix for obtaining bom_no 2026-02-04 12:20:43 +00:00
ruthra kumar
a3bd73b9db Merge pull request #52419 from ruthra-kumar/plug_payment_request_vulnerability
fix: enfore permission on make_payment_request
2026-02-04 17:44:27 +05:30
ruthra kumar
8db29b0a81 refactor: patch partner_website for old data 2026-02-04 17:43:31 +05:30
ruthra kumar
8cf31548f2 refactor: scrub http and use https in sales partner 2026-02-04 17:43:29 +05:30
Deepesh Garg
3a91f6793f refactor: Add advance settings 2026-02-04 17:35:27 +05:30
Shllokkk
ced2f33a63 fix: correct link filter for processes child table procedure field 2026-02-04 17:22:19 +05:30
Deepesh Garg
954173d629 refactor: Add advance settings 2026-02-04 17:16:40 +05:30
Rohit Waghchaure
f3ea1863ae refactor: Cleanup buying module forms 2026-02-04 17:07:25 +05:30
Rohit Waghchaure
175fe9279c fix: not able to complete job card 2026-02-04 17:04:13 +05:30
Mihir Kandoi
3b45485351 Merge pull request #51773 from aerele/fix/production-analytics-report 2026-02-04 16:57:23 +05:30
ruthra kumar
b755ca12ca fix: enfore permission on make_payment_request 2026-02-04 16:54:51 +05:30
Deepesh Garg
0687e683f8 refactor: Add advance settings 2026-02-04 16:50:58 +05:30
Pandiyan37
7e08154217 fix(stock): update target field attribute 2026-02-04 15:19:56 +05:30
Deepesh Garg
a734f90920 fix: Add totals and base total section 2026-02-04 12:41:19 +05:30
Sudharsanan11
27091e5168 fix(manufacturing): fix chart period keys 2026-02-04 12:18:03 +05:30
Mihir Kandoi
3f8af9b3c9 Merge pull request #52383 from mihir-kandoi/fix-rate-comparison-3 2026-02-04 12:17:53 +05:30
Mihir Kandoi
e8d1e9d946 fix: return None instead of 0 if valuation rate is falsy 2026-02-04 12:02:14 +05:30
Deepesh Garg
6d64a6a1d1 Merge branch 'develop' of https://github.com/frappe/erpnext into erpnext_form_cleanups 2026-02-04 11:57:05 +05:30
Deepesh Garg
2f2e16fc33 refactor: Better organizing of the fields in various doctypes 2026-02-04 11:18:09 +05:30
Sudharsanan11
16f09141da fix(manufacturing): handle None value for actual_end_date 2026-02-04 11:14:47 +05:30
Diptanil Saha
036f64013d fix: remove customer_pos_id reference (#52396) 2026-02-04 10:47:56 +05:30
Jatin3128
51cfd6119f Merge pull request #51745 from elshafei-developer/fix-translate-report-Gross-Profit
fix(gross profit report): translate column Sales Invoice
2026-02-04 04:43:23 +05:30
ruthra kumar
b719a6c767 fix: group item wise tax details by tax row (#51898) 2026-02-03 21:08:01 +05:30
NaviN
6fde0a6261 fix: merge taxes in purchase receipt when get items from multiple purchase invoices (#51422)
* fix: merge taxes in purchase receipt when get items from multiple purchase invoices

* fix: make merge tax configurable

* chore: follow standard merge taxes method

* chore: follow standard merge taxes method
2026-02-03 21:03:14 +05:30
Mihir Kandoi
671726a3e4 Merge pull request #52259 from aerele/fix/support-#58787 2026-02-03 20:24:43 +05:30
Mihir Kandoi
f1b4fe12a2 fix: rate comparison in stock reco 2026-02-03 20:21:51 +05:30
Mihir Kandoi
4c4d8859f6 Merge pull request #52374 from aerele/fix/fetch-batch-valuation-rate-in-stock-recon 2026-02-03 20:17:12 +05:30
rohitwaghchaure
a3ba0a18c4 Merge pull request #52278 from aerele/feat/add-company-field-bin
feat(stock): add company field to Bin with migration patch
2026-02-03 20:10:44 +05:30
ruthra kumar
2e51e7fb78 Merge pull request #51651 from aerele/ppr-skip-ref-detail
fix: correct exchange gain loss in ppr
2026-02-03 20:05:19 +05:30
ruthra kumar
6387aebed8 Merge pull request #51558 from ili-ad/fix/postgres-customer-autoname
fix(postgres): avoid UNSIGNED cast in customer autoname
2026-02-03 19:48:49 +05:30
ruthra kumar
b478f0da89 Merge pull request #52314 from aerele/fix/journal-entry-exchange-rate-type
fix(journal-entry): normalize exchange rate to float
2026-02-03 19:47:00 +05:30
rohitwaghchaure
6d41e35242 Merge pull request #52375 from frappe/mergify/bp/develop/pr-52369
fix: zero valuation rate if returning from different warehouse (backport #52369)
2026-02-03 19:29:08 +05:30
Rohit Waghchaure
eb2119e292 fix: zero valuation rate if returning from different warehouse
(cherry picked from commit 28929df0e8)
2026-02-03 13:41:15 +00:00
kavin-114
c5df570262 fix(stock): fetch batch wise valuation rate in get_items 2026-02-03 18:36:18 +05:30
Smit Vora
12f8bb2937 test: further tests for query builder 2026-02-03 18:11:34 +05:30
ruthra kumar
7fd6278c18 Merge pull request #52320 from mendozal/fix-sales-tax-template-sidebar-link
fix: correct Sales Tax Template sidebar link to proper DocType
2026-02-03 17:35:14 +05:30
ruthra kumar
c90f77af44 Merge pull request #52279 from aerele/profit-loss-chart-xaxis
fix(profit and loss statement): exclude non period columns
2026-02-03 17:29:18 +05:30
rohitwaghchaure
c29bcbfed8 Merge pull request #52160 from aerele/support-57403
fix(stock): remove is_return condition on pos batch qty calculation
2026-02-03 16:41:09 +05:30
Smit Vora
a29710dc07 test: correct error message 2026-02-03 16:32:26 +05:30
Smit Vora
f45a5a63a7 test: revert original pcv setting 2026-02-03 16:30:05 +05:30
ruthra kumar
82aa32f74e Merge pull request #51997 from aerele/feat/add-partially-billed-status
Add partially billed status indicator
2026-02-03 16:28:24 +05:30
Shllokkk
5793322c30 fix: unhide book_advance_payments_in_separate_party_account check field in Payment Entry doctype 2026-02-03 16:26:35 +05:30
Smit Vora
61d8308e81 test: add tests for query builder 2026-02-03 16:18:27 +05:30
Smit Vora
b41c1858a3 fix: Period Closing Voucher doesn't exist for GL Entry 2026-02-03 16:03:14 +05:30
rohitwaghchaure
4a7d606099 Merge pull request #52338 from rohitwaghchaure/fixed-negative-stock-error-for-purchase-return
fix: negative stock for purchase return
2026-02-03 15:54:39 +05:30
Sudharsanan11
de8f8ef9f4 fix(stock): include subcontracting order qty while calculating the bin qty 2026-02-03 15:37:15 +05:30
Shllokkk
93a4b9d49f Merge pull request #52346 from Shllokkk/ux-fixes
fix: move company field to first position in sales invoice, purchase …
2026-02-03 15:34:33 +05:30
ruthra kumar
9160aeb30e Merge pull request #52339 from sokumon/rename-icons
chore: rename icons
2026-02-03 15:30:20 +05:30
Smit Vora
ee2f8d8ebc fix: correctly calculate running balances for financial report 2026-02-03 15:21:38 +05:30
sokumon
2d312bcfe8 chore: rename icons 2026-02-03 15:21:17 +05:30
ruthra kumar
3ee11307f1 Merge pull request #52345 from ruthra-kumar/ignore_svg
ci: skip svg
2026-02-03 15:19:40 +05:30
Shllokkk
8e9365eb3b fix: move company field to first position in sales invoice, purchase invoice, sales order, purchase order and journal entry 2026-02-03 15:04:39 +05:30
Rohit Waghchaure
77893933a2 fix: negative stock for purchase return 2026-02-03 15:02:59 +05:30
ruthra kumar
e565d2283e ci: skip svg 2026-02-03 14:59:52 +05:30
ruthra kumar
ff2f015d67 Merge pull request #52280 from aerele/fix/support-#57038
fix(stock): ignore packing slip while cancelling the sales invoice
2026-02-03 13:53:51 +05:30
Dharanidharan2813
be0040ddc7 fix(journal-entry): normalize exchange rate to float 2026-02-03 12:51:53 +05:30
kavin-114
12ec997027 test: add unit test case for pos reserved with return qty 2026-02-03 12:43:09 +05:30
Dharanidharan2813
7767000ccf feat(delivery-note): add status indicator when document is partially billed 2026-02-03 12:29:55 +05:30
Praveenkumar26-S
d54259c99c feat(stock): add company field to Bin with migration patch 2026-02-03 11:41:20 +05:30
Nishka Gosalia
46b4cf3add fix: Added validation for quality inspection in job card 2026-02-03 11:38:36 +05:30
ruthra kumar
42f2abf5ae Merge pull request #51655 from aerele/project-gross-margin-credit-note
fix: include credit notes in project gross margin calculation
2026-02-03 11:37:09 +05:30
Mihir Kandoi
dbbeca6308 Merge pull request #52184 from aerele/fix/support-#58126 2026-02-03 09:09:21 +05:30
Luis Mendoza
06a7c85c93 fix: correct Sales Tax Template sidebar link to proper DocType 2026-02-02 20:53:47 +00:00
Dharanidharan S
7a1c4a5ded fix: avoid duplicate taxes and charges rows in payment entry (#52178) 2026-02-03 00:56:02 +05:30
Apriliansyah Idris
6cc802147b fix: duplicate account number (Indonesia COA) (#52080) 2026-02-03 00:22:43 +05:30
Sudharsanan11
4d9412181c test(subcontracting): add test for consumed_qty calculation with similar finished goods 2026-02-02 22:52:30 +05:30
Sudharsanan11
0d372a62a1 fix(subcontracting): include item bom in supplied items grouping key 2026-02-02 22:51:33 +05:30
rohitwaghchaure
62a98a0504 Merge pull request #52303 from rohitwaghchaure/fixed-batch-selector-v16
fix: batch selector not working if Use Legacy (Client side) Reactivity disabled
2026-02-02 22:01:25 +05:30
Mihir Kandoi
135a433018 Merge pull request #52246 from mihir-kandoi/st58765 2026-02-02 15:14:22 +00:00
Mihir Kandoi
af6a1eb189 Merge pull request #52304 from mihir-kandoi/customer-contact-quotation 2026-02-02 20:15:59 +05:30
Mihir Kandoi
a8068b89ed Merge pull request #52281 from aerele/fix/stock-bal-report-opening 2026-02-02 20:06:53 +05:30
Mihir Kandoi
75b2c2c83d fix: populate contact fields when creating quotation from customer 2026-02-02 19:59:53 +05:30
Rohit Waghchaure
d1cba1073f fix: batch selector not working if Use Legacy (Client side) Reactivity disabled 2026-02-02 19:54:27 +05:30
Mihir Kandoi
c176af4fe3 Merge pull request #52286 from mihir-kandoi/reset-incoming-rate 2026-02-02 19:47:04 +05:30
Mihir Kandoi
2d6b43fd54 fix: reset incoming rate in selling controller if there are changes in item 2026-02-02 14:08:27 +05:30
ruthra kumar
20ce96e358 Merge pull request #52200 from Tom-1508/fix-journal-print-date
fix(accounts): correct date in Journal Auditing Voucher print format
2026-02-02 12:51:02 +05:30
Tamal Majumdar
43e2495df8 fix: journal auditing voucher print date to use posting_date 2026-02-02 12:42:38 +05:30
ruthra kumar
0e4c37bb6d Merge pull request #51692 from aerele/fix/payment-reconciliation-spelling
fix: correct spelling of Payment Reconciliation in Accounting
2026-02-02 12:39:33 +05:30
kavin-114
f3eb6c7078 fix(stock): add stock recon opening stock condition 2026-02-02 12:36:09 +05:30
nivithamerlin
35e53d28df fix: correct spelling of Payment Reconciliation in Accounting 2026-02-02 12:24:36 +05:30
ravibharathi656
6180e5eb53 fix(profit and loss statement): exclude non period columns 2026-02-02 12:05:45 +05:30
Sudharsanan11
c58887b44a fix(stock): ignore packing slip while cancelling the sales invoice 2026-02-02 11:59:42 +05:30
Mihir Kandoi
c7fd855092 Merge pull request #52274 from mihir-kandoi/over-order-quotation-test 2026-02-02 10:07:20 +05:30
Mihir Kandoi
53e58f6678 test: over ordering of quotation items 2026-02-02 09:51:09 +05:30
MochaMind
5ea7a91fe1 chore: update POT file (#52265) 2026-02-01 15:07:23 +01:00
Mihir Kandoi
200cefa0a5 Merge pull request #52253 from mihir-kandoi/st58854 2026-01-31 20:03:17 +05:30
Mihir Kandoi
f5d74b4572 Merge pull request #52252 from mihir-kandoi/item-report-view-blank 2026-01-31 19:50:51 +05:30
Mihir Kandoi
d9998a977c fix: make item name editable in RFQ 2026-01-31 19:47:51 +05:30
Mihir Kandoi
b24ae5e9a2 fix: better fix for aac39b2671 2026-01-31 19:42:27 +05:30
rohitwaghchaure
54d96bb761 Merge pull request #52232 from rohitwaghchaure/fixed-validate-repack-stock-entry
fix: validation when more than one FG items in repack stock entry
2026-01-31 12:46:02 +05:30
Rohit Waghchaure
6423ce2fa7 fix: validation when more than one FG items in repack stock entry 2026-01-31 12:30:18 +05:30
Mihir Kandoi
6066fef1c9 Merge pull request #52231 from UmakanthKaspa/fix/report-view-item-code 2026-01-30 22:15:40 +05:30
UmakanthKaspa
b20f57321f fix: item code not showing in report view 2026-01-30 14:13:34 +00:00
Mihir Kandoi
33b6fd408f Merge pull request #52222 from mihir-kandoi/st56549 2026-01-30 19:30:05 +05:30
Mihir Kandoi
36f1e3572c fix: test cases 2026-01-30 19:16:39 +05:30
Mihir Kandoi
4cc306d2d8 fix: validate over ordering of quotation 2026-01-30 18:36:24 +05:30
Mihir Kandoi
3e9a3c1185 Merge pull request #52226 from mihir-kandoi/failing-tests 2026-01-30 17:47:44 +05:30
Mihir Kandoi
d3f44a425c fix: failing test cases 2026-01-30 17:30:50 +05:30
Mihir Kandoi
058be326f0 Merge pull request #51433 from mihir-kandoi/naming-rule-based-on-posting-date 2026-01-30 17:10:50 +05:30
Mihir Kandoi
46b742cd4c Merge pull request #52223 from mihir-kandoi/gh52220 2026-01-30 17:02:10 +05:30
Mihir Kandoi
95fdbe55f9 fix: allow sales invoice to be renamed 2026-01-30 16:46:41 +05:30
Mihir Kandoi
fc8647d1da Merge pull request #52209 from mihir-kandoi/fix-precision-pr 2026-01-30 12:05:16 +05:30
Mihir Kandoi
f82f1da706 Merge pull request #52213 from mihir-kandoi/st58513 2026-01-30 11:58:53 +05:30
Mihir Kandoi
6e17ccf499 fix: hide close button on WO if WO is completed 2026-01-30 11:56:38 +05:30
Mihir Kandoi
40bfd08866 Merge pull request #52210 from mihir-kandoi/gh52189 2026-01-30 11:46:49 +05:30
Mihir Kandoi
89f6f0f46f fix(barcode): failing request when item has both batch and serial 2026-01-30 11:37:21 +05:30
Mihir Kandoi
838d245215 fix: add precision to rejected batch no qty calculation 2026-01-30 10:19:35 +05:30
Dany Robert
b565dd3da8 fix: missing depr_series causing error on jv creation (#52085) 2026-01-29 23:10:04 +05:30
Mihir Kandoi
e1b6ec340c Merge pull request #52201 from mihir-kandoi/gh52199 2026-01-29 21:42:29 +05:30
Mihir Kandoi
c38f884095 fix: hide item_wise_tax_details table from print 2026-01-29 21:25:39 +05:30
rohitwaghchaure
464560a949 Merge pull request #52190 from rohitwaghchaure/fixed-lead-time-calculation-for-fg
fix: lead time calculation for FG item
2026-01-29 17:58:44 +05:30
rohitwaghchaure
f7f2e73f79 Add Landed Cost Voucher Amount in Internal Purchase Receipt (#52158)
* fix(stock): set incoming_rate with lcv rate for internal purchase

* test: add unit test to check internal purchase with lcv
2026-01-29 17:32:13 +05:30
Rohit Waghchaure
646688c291 fix: lead time calculation for FG item 2026-01-29 17:30:22 +05:30
kavin-114
dd4fd89ef8 test: add unit test to check internal purchase with lcv 2026-01-29 16:48:39 +05:30
kavin-114
f0dccc3cd7 fix(stock): set incoming_rate with lcv rate for internal purchase 2026-01-29 16:48:32 +05:30
Mihir Kandoi
91b1df49a4 Merge pull request #52181 from mihir-kandoi/st58663 2026-01-29 15:30:29 +05:30
Mihir Kandoi
7f6f39f5e7 fix: js error if user does not have write permission for date field 2026-01-29 15:28:55 +05:30
Jacob Salvi
cdcf3fa593 refactor: new accounting icons (#52173)
* chore: new icons share-management

* chore: new accounting icons

* chore: trigger CI

---------

Co-authored-by: Soham Kulkarni <77533095+sokumon@users.noreply.github.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2026-01-29 14:42:47 +05:30
jacob-salvi
728c678cf9 chore: new accounting icons 2026-01-29 11:47:57 +05:30
Diptanil Saha
e11ba21b42 fix(demo): removed toolbar eventlistener (#52171) 2026-01-29 05:57:12 +00:00
Mihir Kandoi
1a4ecba742 Merge pull request #52166 from mihir-kandoi/gh52113 2026-01-29 10:49:53 +05:30
Mihir Kandoi
4e19c7e8bd fix: production plan not considering planning datetime when creating WO 2026-01-29 10:16:06 +05:30
Aarol D'Souza
578b06e027 Merge pull request #52092 from AarDG10/fix-email-render-rfq
fix(RFQ): render email templates for preview and sending
2026-01-29 09:06:51 +05:30
kavin-114
2c19c1fd06 fix(stock): remove is_return condition on pos batch qty calculation 2026-01-29 02:17:20 +05:30
Soham Kulkarni
3d65db2ac3 feat: clear demo data from desktop screen (#52128) 2026-01-28 17:13:22 +05:30
rohitwaghchaure
fabc26bb69 Merge pull request #52007 from aerele/fix/set-zero-rate-for-expired-batch
Fix: Set Zero Rate for Standalone Credit Note with Expired Batch
2026-01-28 16:12:37 +05:30
mahsem
27226b1d82 chore: delete swedish 2024 chart of accounts template (#52032) 2026-01-28 15:09:48 +05:30
Mihir Kandoi
0dc804f9b4 Merge pull request #51961 from aerele/sales-order-project-dimensions 2026-01-27 21:53:39 +05:30
Mihir Kandoi
3192f3f011 Merge pull request #52084 from harrishragavan/fix/shipment-field-validation 2026-01-27 21:26:42 +05:30
harrishragavan
3c6eb9a531 fix(shipment): user contact validation to use full name 2026-01-27 20:52:26 +05:30
Soham Kulkarni
8dae178728 Merge pull request #52119 from sokumon/blue-icons 2026-01-27 20:43:51 +05:30
sokumon
6f9cd8c261 chore: change color of icons in accounting folders 2026-01-27 20:15:00 +05:30
ruthra kumar
d6189b8101 Merge pull request #51894 from ruthra-kumar/refactor_accounting_workspace
refactor: accounting workspace
2026-01-27 18:45:10 +05:30
Nikhil Kothari
48f4a44fb5 feat(accounts): retain filters when switching between financial statements (#51668) 2026-01-27 18:38:37 +05:30
ruthra kumar
f0332c4dc7 refactor: reuse icon for invoicing 2026-01-27 18:30:03 +05:30
NaviN
ec41f1b0f5 fix(asset capitalization): update total_asset_cost on asset capitalisation submission (#52077)
fix(asset capitalization): update total_asset_cost on asset capitalization submission
2026-01-27 18:04:04 +05:30
madelyngamble2
7e9647f3f0 fix: unable to split asset from capitalization (#52020)
* fix: Allow split asset from capitalized composite asset (fixes #52016)

* test: Add test case for splitting asset created via capitalization (fixes #52016)

* docs: Add docstring to before_submit method

* fix: Remove unused variable and fix UTF-8 encoding in asset files

* fix: Remove UTF-8 BOM from asset.py to fix linting

* fix: Fix test_split_asset_created_via_capitalization test parameters

* fix: Remove unused import create_item

* chore: remove unnecessary comments

Removed validation comments for composite asset capitalization in before_submit method.

---------

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2026-01-27 17:46:58 +05:30
ruthra kumar
fb9656b975 refactor: rename Accounts to Accounting 2026-01-27 17:23:40 +05:30
Mihir Kandoi
1b6fe8498d Merge pull request #52106 from mihir-kandoi/gh34977 2026-01-27 16:27:14 +05:30
Mihir Kandoi
5eeebbde7f test: fix tests 2026-01-27 16:10:44 +05:30
ruthra kumar
f7abf9c1da refactor: link payments dashboard to sidebar 2026-01-27 15:52:57 +05:30
ruthra kumar
99406ccc15 refactor: payments dashboard 2026-01-27 15:52:57 +05:30
ruthra kumar
1295d7aa30 refactor: shed duplicates from invoicing sidebar 2026-01-27 15:52:57 +05:30
ruthra kumar
5a680d5037 refactor: reorder accounts setup sidebar 2026-01-27 15:52:57 +05:30
ruthra kumar
7528d42187 refactor: introduce setup icon and reorder 2026-01-27 15:52:57 +05:30
ruthra kumar
cbdc945287 refactor: payments sidebar and icon 2026-01-27 15:52:57 +05:30
ruthra kumar
faf0dcb102 chore: rename accounting to invoicing 2026-01-27 15:52:57 +05:30
ruthra kumar
5e02b4009e chore: remove accounting icon 2026-01-27 15:52:57 +05:30
ruthra kumar
8125f9035c refactor: invoicing icon 2026-01-27 15:52:57 +05:30
Vishnu Priya Baskaran
efa3973b77 fix: check the payment ledger entry has the dimension (#51823)
* fix: check the payment ledger entry has the dimension

* fix: add project in payment ledger entry
2026-01-27 15:45:33 +05:30
Mihir Kandoi
71371b0ba5 fix: show everything else besides other party specific item 2026-01-27 15:43:58 +05:30
SowmyaArunachalam
543b6e51c0 fix: handle parent level project change 2026-01-27 15:37:14 +05:30
kavin-114
3460a7efb5 test(credit-note): add unit test for zero valuation rate on expired batch 2026-01-27 15:13:46 +05:30
kavin-114
e78c750b4e fix(credit-note): set incoming rate as zero for expired batch 2026-01-27 15:13:34 +05:30
Dharanidharan S
d82c92a237 fix(accounts): correct base grand total and rounded total mismatch (#51739) 2026-01-27 14:08:16 +05:30
Mihir Kandoi
826cf66af8 Merge pull request #52088 from mihir-kandoi/gh51577 2026-01-27 12:19:58 +05:30
Mihir Kandoi
b49c679a50 fix: show message if image is removed from item description 2026-01-27 12:04:49 +05:30
NaviN
5f05714e9d fix(payment entry): update currency symbol (#51956) 2026-01-27 12:01:11 +05:30
AarDG10
37cdae2f34 ci: minor text correction 2026-01-27 11:59:18 +05:30
SowmyaArunachalam
3b27f49d79 chore: use frappe.model.set_value 2026-01-27 11:36:51 +05:30
AarDG10
525b3960e1 fix(RFQ): render email templates for preview and sending 2026-01-27 11:35:50 +05:30
kavin-114
04cdf88715 feat(credit-note): add checkbox to set valuation rate as zero for expired batch 2026-01-27 11:04:36 +05:30
V Shankar
f8f626975f fix(journal-entry): prevent submit failure due to double background queuing (#52083) 2026-01-27 11:00:38 +05:30
rohitwaghchaure
31c536e33f Merge pull request #52062 from rohitwaghchaure/fixed-github-52028
fix: not able to complete the job card
2026-01-26 23:14:20 +05:30
Mihir Kandoi
c1fef8269a Merge pull request #52064 from Shankarv19bcr/customer-auto-name-fix 2026-01-26 15:16:21 +05:30
Shankarv19bcr
e5ba0e6401 fix: strip whitespace in customer_name 2026-01-26 14:37:55 +05:30
Rohit Waghchaure
696ea68f86 fix: not able to complete the job card 2026-01-26 11:35:48 +05:30
MochaMind
71d00f5290 chore: update POT file (#52057) 2026-01-25 20:37:19 +01:00
Henning Wendtland
0fb37ad792 feat(Transaction Deletion Record): Editable "DocTypes To Delete" List with CSV import/export (#50592)
* feat: add editable DocTypes To Delete list with import/export

Add user control over transaction deletion with reviewable and reusable deletion templates.

- New "DocTypes To Delete" table allows users to review and customize what will be deleted before submission
- Import/Export CSV templates for reusability across environments
- Company field rule: only filter by company if field is specifically named "company", otherwise delete all records
- Child tables (istable=1) automatically excluded from selection
- "Remove Zero Counts" helper button to clean up list
- Backward compatible with existing deletion records

* refactor: improve Transaction Deletion Record code quality

- Remove unnecessary chatty comments from AI-generated code
- Add concise docstrings to all new methods
- Remove redundant @frappe.whitelist() decorators from internal methods
- Improve CSV import validation (header check, child table filtering)
- Add better error feedback with consolidated skip messages
- Reorder form fields: To Delete list now appears before Excluded list
- Add conditional visibility for Summary table (legacy records only)
- Improve architectural clarity: single API entry point per feature

Technical improvements:
- export_to_delete_template_method and import_to_delete_template_method
  are now internal helpers without whitelist decorators
- CSV import now validates format and provides detailed skip reasons
- Summary table only shows for submitted records without To Delete list
- Maintains backward compatibility for existing deletion records

* fix: field order

* test: fix broken tests and add new ones

* fix: adapt create_transaction_deletion_request

* test: fix assertRaises trigger

* fix: conditionally execute Transaction Deletion pre-tasks based on selected DocTypes

* refactor: replace boolean task flags with status fields

* fix: remove UI comment

* fix: don't allow virtual doctype selection and improve protected Doctype List

* fix: replace outdated frappe.db.sql by frappe.qb

* feat: add support for multiple company fields

* fix: autofill comapny field, add docstrings, filter for company_field

* fix: add edge case handling for update_naming_series and add tests for prefix extraction

* fix: use redis for running deletion validation, check per doctype instead of company
2026-01-25 14:20:28 +05:30
rohitwaghchaure
88069779b2 Merge pull request #52050 from mahsem/swedish_address_template
fix: swedish_address_template
2026-01-25 10:51:39 +05:30
rohitwaghchaure
c5a4164a6b Merge pull request #52043 from rohitwaghchaure/fixed-uom-not-fetching-in-bom
fix: UOM of item not fetching in BOM
2026-01-25 10:44:28 +05:30
mahsem
334e8ada30 fix: swedish_address_template 2026-01-24 23:41:30 +01:00
Rohit Waghchaure
ba8eadda52 fix: UOM of item not fetching in BOM 2026-01-24 14:15:56 +05:30
Smit Vora
297a2ea259 Merge pull request #51691 from Abdeali099/do-not-warn-filter-missing 2026-01-24 12:37:45 +05:30
Smit Vora
e129e1438e Merge pull request #51670 from Abdeali099/fix-bank-quik-entry-erroe 2026-01-24 12:36:46 +05:30
HALFWARE
e810cd8440 fix: update country_wise_tax.json for Algerian Taxes (#51878)
* Algeria chart of accounts

Algeria chart of accounts

* Update Algeria Chart Of Account

* Algeria chart of account

* Algeria Chart of Account

Algeria Chart of Account

* Modify Algeria tax entries in country_wise_tax.json

Updated tax rates and account names for Algeria.

* Rename account for Algeria tax from VAT to TVA

Rename account for Algeria tax from VAT to TVA
2026-01-24 12:02:39 +05:30
El-Shafei H.
50b3396064 fix: Ensure paid_amount is always numeric before calling allocate_amount_to_references (#50935)
fix: ensure paid_amount is not null in allocate_party_amount_against_ref_docs
2026-01-24 11:47:38 +05:30
Abdeali Chharchhoda
9322095786 refactor: use console.error for error logging in Plaid integration 2026-01-24 11:34:34 +05:30
Abdeali Chharchhoda
8a1b8259bd fix: handle undefined bank_transaction_mapping in quick entry 2026-01-24 11:33:42 +05:30
Abdeali Chharchhoda
d905f78984 refactor: not warn when filter field is missing in FS reports 2026-01-24 11:21:17 +05:30
rohitwaghchaure
7250ee4429 Merge pull request #52024 from rohitwaghchaure/fixed-support-56966
fix: Bin reserved qty for production for extra material transfer
2026-01-23 21:13:20 +05:30
ljain112
2068299766 fix: prevent precision errors in discount distribution with inclusive tax 2026-01-23 20:30:07 +05:30
Rohit Waghchaure
f5378b6573 fix: Bin reserved qty for production for extra material transfer 2026-01-23 19:35:47 +05:30
Diptanil Saha
c4e35f1284 Merge pull request #51976 from diptanilsaha/gh_51348 2026-01-23 16:30:27 +05:30
Mihir Kandoi
6a9c0e22de Merge pull request #51999 from aerele/support/fix-58134 2026-01-23 14:03:26 +05:30
rohitwaghchaure
809b29fe90 Merge pull request #52006 from rohitwaghchaure/fixed-negative-stock-for-purchase-return
fix: negative stock for purchase return
2026-01-23 11:33:25 +05:30
Rohit Waghchaure
d68a04ad16 fix: negative stock for purchae return 2026-01-23 01:19:52 +05:30
rohitwaghchaure
6954811c55 Merge pull request #52003 from rohitwaghchaure/fixed-hide-stock-entry-button
fix: do not show stock entry button if timer is running
2026-01-22 23:56:54 +05:30
rohitwaghchaure
8cdc21c264 Merge pull request #51989 from aerele/fix/autofill-warehouse
fix: autofill warehouse for packed items
2026-01-22 22:57:16 +05:30
Rohit Waghchaure
466668c6b8 fix: do not show stock entry button if timer is running 2026-01-22 22:56:34 +05:30
MochaMind
e7e22c809e fix: sync translations from crowdin (#51913)
* fix: Arabic translations

* fix: Arabic translations

* fix: Arabic translations

* fix: Arabic translations
2026-01-22 13:52:18 +00:00
Bharathidhasan06
2606ca6fa9 fix(stock): use purchase UOM in Supplier Quotation items 2026-01-22 18:07:36 +05:30
diptanilsaha
54acaa2aec fix(payment_reconciliation): retain journal entry accounts child table order 2026-01-22 14:06:56 +05:30
Khushi Rawat
a030ea6fde Merge pull request #51756 from aerele/asset-repair
fix: disable asset repair when status is fully depreciated
2026-01-22 13:59:39 +05:30
Sudharsanan11
3f8a0a4833 fix: autofill warehouse for packed items 2026-01-22 12:54:51 +05:30
Mihir Kandoi
e5d25a7f04 Merge pull request #51908 from ljain112/fix-inward-subcontracting 2026-01-22 10:34:44 +05:30
Mihir Kandoi
40e86b6670 Merge pull request #51929 from ljain112/fix-subcontracting-inward-rm-rate 2026-01-22 10:31:53 +05:30
Mihir Kandoi
d0c9924c37 Merge pull request #51966 from aerele/customer-group-filters 2026-01-22 10:26:22 +05:30
Mihir Kandoi
ede4faa152 Merge pull request #51967 from aerele/project-update-naming-series 2026-01-22 10:22:31 +05:30
Mihir Kandoi
05cf1dcab8 Merge pull request #51968 from mihir-kandoi/gh51965 2026-01-21 22:50:22 +05:30
Mihir Kandoi
43a6dd5657 Merge pull request #51964 from mihir-kandoi/dont-show-dn-btn-if-not-reqd 2026-01-21 22:39:08 +05:30
Mihir Kandoi
343ee9695b fix: rejected qty in PR doesn't consider conversion factor 2026-01-21 22:33:24 +05:30
ravibharathi656
49e64f4e1c fix(project): add missing counter to project update naming series 2026-01-21 19:55:33 +05:30
SowmyaArunachalam
1e3db9f916 fix(customer): add customer group filters 2026-01-21 17:37:37 +05:30
Mihir Kandoi
70ec977cb2 fix: create DN btn should not be shown if it cannot be created 2026-01-21 17:13:35 +05:30
Mihir Kandoi
d6bbe43fa0 Merge pull request #51958 from mihir-kandoi/force-serial-batch-stock-reco 2026-01-21 16:23:18 +05:30
SowmyaArunachalam
9e51701e2a fix(sales order): set project at item level from parent 2026-01-21 16:11:04 +05:30
Mihir Kandoi
035b3cb61e fix: tests 2026-01-21 15:58:46 +05:30
Mihir Kandoi
97c36d1edc Merge pull request #51947 from mihir-kandoi/st57849 2026-01-21 15:46:38 +05:30
Mihir Kandoi
7170a1bd78 fix: force user to enter batch or serial for serial/batch items 2026-01-21 15:30:55 +05:30
Diptanil Saha
936f13eb20 ci: generate pot files for version-16-hotfix (#51954) 2026-01-21 14:37:21 +05:30
Mihir Kandoi
ed51db3217 Merge pull request #51948 from mihir-kandoi/st57735 2026-01-21 13:05:43 +05:30
Mihir Kandoi
5bacb67d36 fix: warehouse permissions in MR incorrectly ignored 2026-01-21 12:50:50 +05:30
Mihir Kandoi
c919b1de38 fix: job cards should not be deleted on close of WO 2026-01-21 11:46:00 +05:30
Mihir Kandoi
46ab5e8e46 Merge pull request #51934 from mihir-kandoi/st57619 2026-01-20 20:46:38 +05:30
Mihir Kandoi
3960c01798 fix: validation message in stock reco row idx 2026-01-20 20:30:10 +05:30
rohitwaghchaure
3c5071cefc Merge pull request #51930 from frappe/revert-51920-fixed-do-reposting-for-lcv
Revert "perf: prevent duplicate reposting for the same item"
2026-01-20 19:48:35 +05:30
rohitwaghchaure
6e4b90055f Revert "perf: prevent duplicate reposting for the same item" 2026-01-20 19:30:42 +05:30
ljain112
37ee560eae fix: calculate weighted average rate for customer provided items in subcontracting inward order 2026-01-20 18:48:59 +05:30
Mihir Kandoi
4b3000b071 Merge pull request #51909 from mihir-kandoi/gh51906 2026-01-20 17:44:23 +05:30
rohitwaghchaure
ad6cb177e3 Merge pull request #51920 from rohitwaghchaure/fixed-do-reposting-for-lcv
perf: prevent duplicate reposting for the same item
2026-01-20 17:38:15 +05:30
ljain112
d256365f4a fix: throw if item order field is not set in subcontracting controller 2026-01-20 17:33:01 +05:30
Diptanil Saha
05fea7f66f Merge pull request #51887 from diptanilsaha/bank_ac 2026-01-20 17:21:51 +05:30
Rohit Waghchaure
7535931571 perf: prevent duplicate reposting for the same item 2026-01-20 17:13:16 +05:30
Mihir Kandoi
27915c9ce2 Merge pull request #51914 from mihir-kandoi/st57262 2026-01-20 16:44:43 +05:30
ruthra kumar
93b131f48a Merge pull request #51671 from nikkothari22/advance-taxes-dimensions
fix(accounts): add missing accounting dimensions in advance taxes and charges
2026-01-20 16:36:16 +05:30
Nikhil Kothari
10d5463a40 Merge branch 'develop' into advance-taxes-dimensions 2026-01-20 16:19:08 +05:30
Mihir Kandoi
017cc9d9f9 fix: continuous raw material consumption with bom validation 2026-01-20 16:18:06 +05:30
Mihir Kandoi
b691de0147 fix: allow creation of DN in SI for items not having DN reference 2026-01-20 15:08:37 +05:30
rohitwaghchaure
beabbb1fa2 Merge pull request #51900 from rohitwaghchaure/fixed-github-49961
fix: validation to check at-least one raw material for manufacture entry
2026-01-20 13:54:48 +05:30
rohitwaghchaure
65c3020d1b Merge pull request #51899 from rohitwaghchaure/fixed-github-51401
feat: option to import serial / batches using csv for outward entry
2026-01-20 13:37:35 +05:30
Rohit Waghchaure
f003b3c378 fix: validation to check at-least one raw material for manufacture entry 2026-01-20 13:36:46 +05:30
Rohit Waghchaure
a268316322 feat: option to import serial / batches using csv for outward entry 2026-01-20 13:10:40 +05:30
Mihir Kandoi
090dabeea5 Merge pull request #51895 from mihir-kandoi/st57390 2026-01-20 13:03:08 +05:30
Mihir Kandoi
edba9efb5e fix: overproduction % not considered when making WO from SO 2026-01-20 12:48:03 +05:30
Diptanil Saha
ad205546c3 fix: collapsible filters on accounts receivable and accounts payables reports (#51798)
Co-authored-by: sokumon <sohamkulkarns9@gmail.com>
2026-01-20 12:44:49 +05:30
ravibharathi656
57bd1facf5 fix: group item wise tax details by tax row 2026-01-20 12:31:30 +05:30
ravibharathi656
a378fee8e0 fix: include credit notes in project gross margin calculation 2026-01-20 08:47:58 +05:30
ruthra kumar
47ee9ce0e2 Merge pull request #51886 from barredterra/translatable-return-msg
fix(accounts_controller): make return message translatable
2026-01-20 07:55:47 +05:30
Lakshit Jain
aea70c5ec1 Merge pull request #51561 from ljain112/fic-adv-ple-po
fix: delete advance ledger entries  while reconciling payment entry
2026-01-20 07:50:27 +05:30
diptanilsaha
7532ab01d6 fix(bank_account): validation for is_company_account 2026-01-20 00:46:46 +05:30
barredterra
0209f0fe29 fix(accounts_controller): make return message translatable 2026-01-19 17:55:50 +01:00
rohitwaghchaure
51fd15e2af Merge pull request #51830 from aerele/fix/work-order-produced-qty
fix(manufacturing): consider process loss qty while validating the work order
2026-01-19 21:46:06 +05:30
Mihir Kandoi
7b5f69bae8 Merge pull request #51880 from mihir-kandoi/gh51873 2026-01-19 20:04:26 +05:30
Mihir Kandoi
11d198fcd6 Merge pull request #51879 from mihir-kandoi/gh51875 2026-01-19 19:59:42 +05:30
Mihir Kandoi
ad11914fca fix: no attribute error on LCV 2026-01-19 19:49:01 +05:30
Mihir Kandoi
fbac8b032e fix: no attribute error on subcontracting receipt 2026-01-19 19:43:31 +05:30
Abdeali Chharchhoda
63d71ff90a Merge branch 'develop' into fixing-emp-contacts 2026-01-19 17:55:55 +05:30
Diptanil Saha
04a2a52639 Merge pull request #51595 from FHenry/dev_fr_chertofaccount_2025 2026-01-19 17:52:47 +05:30
Diptanil Saha
7dc8b74aa1 Merge pull request #51860 from frappe/pot_develop_2026-01-19 2026-01-19 15:42:08 +05:30
rohitwaghchaure
15047235cb Merge pull request #51769 from aerele/pos-set-warehouse-reset
fix(pos): reapply set warehouse during cart update
2026-01-19 15:35:53 +05:30
rohitwaghchaure
020bdfb5bc Merge pull request #51690 from nishkagosalia/gh-49830
feat: Adding Item name in update item dialog box
2026-01-19 15:29:08 +05:30
rohitwaghchaure
3a85c38417 Merge pull request #51856 from rohitwaghchaure/fixed-serial-no-count
fix: qty with serial no count
2026-01-19 15:24:43 +05:30
frappe-pr-bot
60ed4ada10 chore: update POT file 2026-01-19 09:52:35 +00:00
Khushi Rawat
4b27bcd432 Merge pull request #51822 from aerele/check-dimensions
fix(budget variance report): check budget dimensions
2026-01-19 15:22:23 +05:30
Diptanil Saha
a2ae2c1a1a Merge pull request #51819 from earona/patch-1 2026-01-19 15:21:08 +05:30
Rohit Waghchaure
56e58ef301 fix: qty with serial no count 2026-01-19 15:13:08 +05:30
rohitwaghchaure
7102036500 Refactor batch bundle get snos sle (#51644)
* refactor: Batch & Bundle get Stock ledger for snos

* refactor: Batch & Bundle get Stock ledger for snos v2

* refactor: Batch & Bundle get Stock ledger for snos - added posting date in select

* refactor: Batch & Bundle get sle for snos - Added docstring

* chore: fix semantic commit message

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2026-01-19 14:52:20 +05:30
Rohit Waghchaure
dfcbee9cc0 chore: fix semantic commit message 2026-01-19 14:22:32 +05:30
krupalvora
22dee50348 refactor: Batch & Bundle get sle for snos - Added docstring 2026-01-19 14:21:19 +05:30
krupalvora
1ccc7365a7 refactor: Batch & Bundle get Stock ledger for snos - added posting date in select 2026-01-19 14:21:18 +05:30
krupalvora
a074d81754 refactor: Batch & Bundle get Stock ledger for snos v2 2026-01-19 14:21:18 +05:30
krupalvora
c0149925ad refactor: Batch & Bundle get Stock ledger for snos 2026-01-19 14:21:17 +05:30
Mihir Kandoi
d8d74236dd Merge pull request #51845 from aerele/bom-company-filter 2026-01-19 14:03:15 +05:30
22-poojashree
73bcfc4710 fix(bom): pass company warehouse filter 2026-01-19 13:30:07 +05:30
mahsem
0c0f43f7f7 fix: common_party_path (#51826)
* fix: common_pary_path

* chore: remove non-existent anchor

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2026-01-19 07:49:30 +00:00
MochaMind
998f206da1 fix: sync translations from crowdin (#51704)
* fix: Persian translations

* fix: Hungarian translations

* fix: Hungarian translations

* fix: Hungarian translations
2026-01-19 13:01:37 +05:30
ruthra kumar
e7f6125df8 Merge pull request #51513 from aerele/net-profit-calculation
fix: calculate net profit amount from root node accounts
2026-01-19 12:44:14 +05:30
Lakshit Jain
f00aeec9b4 Merge pull request #51787 from ljain112/fix-taxes-disc
fix: recalculate taxes when item tax template changes after discount
2026-01-19 12:30:29 +05:30
Smit Vora
83919119f8 fix: allow disassemble stock entry without work order (#51761)
* fix: allow disassemble stock entry without work order

* fix: use existing functionality to load fg item

* chore: better dict update
2026-01-19 11:36:12 +05:30
ruthra kumar
9a79beda04 Merge pull request #51742 from aerele/item-wise-sales-register
fix: add other charges in total
2026-01-19 11:13:51 +05:30
Sudharsanan11
e6366e830c fix(manufacturing): consider process loss qty while validating the work order 2026-01-19 11:09:21 +05:30
ruthra kumar
218c255543 Merge pull request #51803 from ljain112/unused-imports
chore: remove unused imports
2026-01-19 10:24:44 +05:30
Mihir Kandoi
167e9c5341 Merge pull request #51827 from trustedcomputer/fix-email-digest 2026-01-19 09:58:41 +05:30
Mihir Kandoi
7cbd644782 Merge pull request #51824 from mihir-kandoi/fg-qty-process-loss-fix 2026-01-18 22:50:21 +05:30
trustedcomputer
d2e01e97f0 fix: remove incorrect validation throwing spurious error 2026-01-18 08:32:46 -08:00
Mihir Kandoi
56f5df6847 fix: setting process loss qty causes fg item qty to be incorrect 2026-01-18 20:11:51 +05:30
ervishnucs
cb696a8880 fix(budget variance report): check budget dimensions 2026-01-18 19:32:51 +05:30
Florian HENRY
b3efb3084f chore: re add older template 2026-01-18 10:46:13 +01:00
Florian HENRY
4fe1b214c1 chore: fix bank account type 2026-01-18 10:43:09 +01:00
Mihir Kandoi
96c3fccb05 Merge pull request #51817 from aerele/fix/barcode-uom-fx-in-item 2026-01-18 15:04:59 +05:30
Florian HENRY
6a876de838 chore: fix CASH acount type 2026-01-17 22:05:53 +01:00
Florian HENRY
765487a087 chore: fix bank acount type 2026-01-17 21:17:43 +01:00
Exequiel Arona
d472888bf0 ci: fix generate POT workflow 2026-01-17 17:06:16 -03:00
Florian HENRY
b83640fae7 Merge branch 'develop' of https://github.com/frappe/erpnext into dev_fr_chertofaccount_2025 2026-01-17 20:57:19 +01:00
Florian HENRY
c519cd0268 chore: add Expenses Included In Valuation account 2026-01-17 20:57:08 +01:00
Pandiyan5273
30263b26a5 fix: prevent UOM from updating incorrectly while scanning barcode 2026-01-17 20:28:04 +05:30
Sowmya
3fe5b5c80d fix: change docfield type to render html format (#51795) 2026-01-17 14:55:25 +05:30
ljain112
e8510287e3 chore: remove unused imports 2026-01-17 14:45:57 +05:30
ruthra kumar
310cca6939 Merge pull request #51555 from ili-ad/fix/postgres-company-month-sales
fix(postgres): compute current month sales without DATE_FORMAT
2026-01-16 16:58:22 +05:30
Mihir Kandoi
e51b7155aa Merge pull request #51790 from aerele/fix/support-57170 2026-01-16 16:17:56 +05:30
Mihir Kandoi
96ade0b821 Merge pull request #51791 from mihir-kandoi/item-fields-visibility 2026-01-16 16:00:25 +05:30
Mihir Kandoi
b3db2981de fix: dont show certain fields based on permissions 2026-01-16 15:58:02 +05:30
Mihir Kandoi
0d7b2d812c Merge pull request #51784 from aerele/warehouse-filter 2026-01-16 15:13:56 +05:30
Pandiyan5273
f959b2c59a fix(stock): resolve quantity issue when adding items via barcode scan 2026-01-16 15:12:28 +05:30
Mihir Kandoi
7549f1ba95 Merge pull request #51788 from mihir-kandoi/js-errors 2026-01-16 15:11:53 +05:30
Mihir Kandoi
047343ca11 fix: js error on customer doctype 2026-01-16 15:10:31 +05:30
Mihir Kandoi
e2c3d0fa94 test: add test case 2026-01-16 14:52:49 +05:30
Mihir Kandoi
b8d4522ea1 chore: make feature opt in 2026-01-16 14:00:42 +05:30
Mihir Kandoi
22fd1a1cfd feat: document naming rule will now use posting date of the document 2026-01-16 14:00:42 +05:30
Mihir Kandoi
876c815bd8 Merge pull request #51693 from mihir-kandoi/sample-retention-refactor 2026-01-16 13:53:50 +05:30
Mihir Kandoi
8fd1d6aec8 chore: typo 2026-01-16 13:38:38 +05:30
rohitwaghchaure
589a393b5c fix: opening stock not working for serial / batch (#51781) 2026-01-16 13:15:24 +05:30
Mihir Kandoi
19ae405742 fix: bugs 2026-01-16 13:15:22 +05:30
SowmyaArunachalam
f952b92d71 fix: add company filters for warehouse 2026-01-16 13:15:00 +05:30
Mihir Kandoi
b567184dd7 test: add test case 2026-01-16 12:31:54 +05:30
Jatin3128
c5b0787de6 Merge pull request #51673 from Jatin3128/ar/ap-future-range-fix
fix: add below-0 column in ar/ap report
2026-01-16 12:06:22 +05:30
Mihir Kandoi
3d0f649411 feat: support for serial item 2026-01-16 10:23:11 +05:30
Mihir Kandoi
b54067e04d fix: remove already transferred batch 2026-01-16 10:23:11 +05:30
Mihir Kandoi
8d188cd32b refactor: sample retention stock entry 2026-01-16 10:23:11 +05:30
Diptanil Saha
5ebaee03da Merge pull request #51764 from diptanilsaha/hook_dv 2026-01-15 18:02:06 +05:30
rohitwaghchaure
7ff31a1d91 Merge pull request #51768 from rohitwaghchaure/fixed-stock-and-account-value-comparision-report
fix: Show non-SLE vouchers with GL entries in Stock vs Account Value …
2026-01-15 17:49:24 +05:30
Rohit Waghchaure
1db9ce205f fix: Show non-SLE vouchers with GL entries in Stock vs Account Value Comparison report 2026-01-15 17:04:49 +05:30
ravibharathi656
5a53c45321 fix(pos): reapply set warehouse during cart update 2026-01-15 17:03:16 +05:30
diptanilsaha
050ea96cc6 chore(hooks): develop_version bump 2026-01-15 15:14:27 +05:30
Mihir Kandoi
fb6e0be5fe Merge pull request #51753 from mahsem/docs_path 2026-01-14 21:29:33 +05:30
SowmyaArunachalam
66fe1aa85d fix: disable asset repair when status is fully depreciated 2026-01-14 21:22:10 +05:30
mahsem
7ef8c81caf fix: docs_path 2026-01-14 16:36:22 +01:00
rohitwaghchaure
2f3d4ddc58 Merge pull request #51729 from rohitwaghchaure/fixed-valuation-for-non-batchwise-valuation
fix: valuation rate for non batchwise valuation
2026-01-14 19:35:20 +05:30
Diptanil Saha
257f0c338c Merge pull request #51730 from diptanilsaha/st_56828 2026-01-14 15:51:09 +05:30
SowmyaArunachalam
9406c07c42 fix: add other charges in total 2026-01-14 12:49:30 +05:30
Ankush Menat
1d35e2b261 build: bump required frappe version (#51738) 2026-01-14 11:07:06 +05:30
Mihir Kandoi
0643beb079 Merge pull request #51684 from aerele/test/manufacture-entry-without-wo 2026-01-14 11:03:19 +05:30
Mihir Kandoi
22e0ca2d7e Merge pull request #51295 from aerele/partial-billing-timesheet 2026-01-14 11:02:18 +05:30
Mihir Kandoi
ce7be9fad5 Merge pull request #51733 from mihir-kandoi/gh51731 2026-01-14 10:24:36 +05:30
Mihir Kandoi
6d3f6d73d0 fix: add uom js error 2026-01-14 10:22:59 +05:30
diptanilsaha
8b445e04e5 fix(transaction.js): use flt instead of cint for plc_conversion_rate 2026-01-14 01:36:15 +05:30
Rohit Waghchaure
b6312bca9c fix: valuation rate for non batchwise valuation 2026-01-14 00:02:28 +05:30
Mihir Kandoi
201a04c49a Merge pull request #51725 from mihir-kandoi/ci-patch-test-develop-2 2026-01-13 20:12:13 +05:30
Mihir Kandoi
5e2c7a08d3 revert: make CI not run on .github change 2026-01-13 19:57:11 +05:30
Mihir Kandoi
0da98e6769 ci: patch test for v16 branch 2026-01-13 19:29:39 +05:30
rohitwaghchaure
da87f358c4 Merge pull request #51719 from rohitwaghchaure/fixed-github-51715
fix: stock module not opened when no warehouses
2026-01-13 17:19:33 +05:30
Rohit Waghchaure
9de3b07223 fix: stock module not opened when no warehouses 2026-01-13 17:02:48 +05:30
Khushi Rawat
d3cd887f5e Merge pull request #51666 from aerele/fix-asset-value-adjustment-cancel
fix(asset value adjustment): skip cancelling revaluation journal entry if already cancelled
2026-01-13 12:38:44 +05:30
Navin-S-R
d65cd605a1 fix: move validation to before_cancel 2026-01-13 12:16:52 +05:30
Ankush Menat
6ec41fa47e build: Bump dev version 2026-01-13 12:15:21 +05:30
Khushi Rawat
d879a91165 Merge pull request #51509 from khushi8112/fix-test-cases
fix: use system configured float precision for depreciation rate
2026-01-13 11:58:58 +05:30
Khushi Rawat
d21cfae095 Merge pull request #51363 from aerele/asset-partial-sales
fix(asset): handle partial asset sales by splitting remaining quantity
2026-01-13 11:54:20 +05:30
Mihir Kandoi
be5f2b6cf0 Merge pull request #51650 from mihir-kandoi/v16-prep 2026-01-13 11:06:02 +05:30
ruthra kumar
37b3a22825 Merge pull request #51412 from ljain112/fix-tds-customer
fix(tds): correct tax logic for customer
2026-01-13 11:05:31 +05:30
Mihir Kandoi
bb307dec0a chore: add v14
remove when EOL reached
2026-01-13 11:04:17 +05:30
Nabin Hait
3bc58fb46f fix: Redirect to Desktop after signup (#51696) 2026-01-12 19:20:27 +05:30
Navin-S-R
73b038084b fix: prevent manual cancellation of the linked Revaluation Journal Entry 2026-01-12 18:18:22 +05:30
Nishka Gosalia
e6133ad6d4 feat: Adding Item name in update item dialog box 2026-01-12 17:06:49 +05:30
Navin-S-R
eeb6d0e9bf fix: remove the redundant purchase receipt submit 2026-01-12 16:45:04 +05:30
Navin-S-R
ca97f34092 fix: use new_asset instead of asset_doc when checking values after splitting 2026-01-12 16:36:34 +05:30
Saqib Ansari
ccb4b1fbc4 fix: delete outdated desktop icon & sidebar (#51685) 2026-01-12 16:13:46 +05:30
Saqib Ansari
adec530792 ci: ignore server tests on svg changes (#51687) 2026-01-12 16:12:46 +05:30
Pandiyan5273
784e338be4 test(stock-entry): manufacture entry without work order 2026-01-12 15:52:38 +05:30
Jacob Salvi
b5e0b543f7 chore: new icons share-management (#51682) 2026-01-12 10:18:28 +00:00
Khushi Rawat
e87857cee0 Merge pull request #51678 from khushi8112/asset-toggle-reference-doc
fix(asset): properly reset purchase reference and item fields
2026-01-12 15:46:15 +05:30
jacob-salvi
a1192e34d7 chore: new icons share-management 2026-01-12 15:14:59 +05:30
Nabin Hait
02a9c54b74 fix: Subcontracting settings link should point to subcontracting tab of Buying Settings (#51680) 2026-01-12 14:08:30 +05:30
Nabin Hait
8434efd11b fix: removed duplicate sidebar link: Tree of procedures (#51679) 2026-01-12 13:22:28 +05:30
khushi8112
671610db1e fix(asset): properly reset purchase reference and item fields 2026-01-12 13:06:48 +05:30
Nabin Hait
1c6bfc9af7 fix: desktop icon for share management and banking (#51676) 2026-01-12 13:06:00 +05:30
Khushi Rawat
b9659a8741 Merge pull request #51630 from aerele/fix/unlink-purchase-flow
fix(asset): remove references  for composite and existing assets
2026-01-12 12:59:32 +05:30
elshafei-developer
3e39d13172 fix(gross profit report): translate column Sales Invoice 2026-01-12 06:56:13 +00:00
rohitwaghchaure
e3beca400f Merge pull request #51656 from aerele/fix-material-transfer-transferred-qty
fix(stock entry): calculate transferred quantity using transfer_qty
2026-01-12 12:04:59 +05:30
Nikhil Kothari
22e9cb4cf4 fix(accounts): add missing accounting dimensions in advance taxes and charges 2026-01-12 00:42:52 +05:30
Abdeali Chharchhoda
7c7ba0154a refactor: remove redundant onload function for bank mapping table 2026-01-12 00:23:02 +05:30
Navin-S-R
500c44e3f5 fix: ignore permissions when cancelling revaluation journal entry 2026-01-11 21:30:09 +05:30
Navin-S-R
5f00239bba refactor(journal entry): replace raw SQL with query builder to unlink asset value adjustment 2026-01-11 19:25:22 +05:30
Navin-S-R
b1704ccef1 fix(asset value adjustment): skip cancelling revaluation journal entry if already cancelled 2026-01-11 19:20:01 +05:30
ruthra kumar
d8ff1595a7 Merge pull request #51662 from frappe/l10n_develop
fix: sync translations from crowdin
2026-01-11 19:03:06 +05:30
ruthra kumar
528de7fbd4 Merge pull request #51664 from ruthra-kumar/reduce_tabs_in_accounts_settings
refactor: UI cleanup in Accounts settings and reports
2026-01-11 19:02:27 +05:30
NaviN
cecd07bbf4 fix(payment reconciliation): handle adhoc payment returns (#51311)
* fix(payment reconciliation): handle reverse payments

* test: validate payment return gain or loss

* chore: typo
2026-01-11 18:57:39 +05:30
ruthra kumar
e66b1a06f4 refactor: remove redundant separators in P&L 2026-01-11 18:38:23 +05:30
ruthra kumar
c15e96c460 refactor: cleanup accounts settings 2026-01-11 18:30:58 +05:30
rohitwaghchaure
78ac8232d8 Merge pull request #51661 from rohitwaghchaure/fixed-single-table-for-better-performance
refactor: single table for better performance
2026-01-11 14:51:50 +05:30
l0gesh29
f7004aa8c3 chore: modify error msg 2026-01-11 14:32:15 +05:30
l0gesh29
8379b39aaf fix: add validation for direct return 2026-01-11 13:52:46 +05:30
Rohit Waghchaure
8d4a179a8f refactor: single table for better performance 2026-01-11 13:43:09 +05:30
MochaMind
058500e011 fix: Indonesian translations 2026-01-11 12:04:33 +05:30
Navin-S-R
bf2ab32abf test: validate transferred quantity for material transfer entry 2026-01-10 20:13:17 +05:30
Navin-S-R
7e99148357 test: allow from_warehouse while creating material request 2026-01-10 19:42:09 +05:30
Navin-S-R
4e6d86d6f0 fix(stock entry): calculate transferred quantity using transfer_qty 2026-01-10 19:29:25 +05:30
Mihir Kandoi
584c40a1b5 Merge pull request #51652 from mihir-kandoi/so_picked_qty 2026-01-10 18:12:53 +05:30
Mihir Kandoi
1d6d9c2040 fix: pick list qty does not reset when pick list is cancelled 2026-01-10 17:57:31 +05:30
ravibharathi656
02e96039ac fix: correct exchange gain loss in ppr 2026-01-10 17:25:42 +05:30
Mihir Kandoi
4987b2fe26 ci: ignore ci folder for tests 2026-01-10 17:01:04 +05:30
Mihir Kandoi
7e7e83440f ci: version 16 related changes 2026-01-10 16:43:39 +05:30
Mihir Kandoi
4cb4b34683 Merge pull request #51648 from mihir-kandoi/gh51636 2026-01-10 15:54:54 +05:30
Mihir Kandoi
7662616721 Merge pull request #51647 from mihir-kandoi/reorder-check-readonly 2026-01-10 15:50:21 +05:30
Mihir Kandoi
9f4bf65768 fix: error message args in sle.py 2026-01-10 15:40:03 +05:30
Mihir Kandoi
7c96a08054 fix: reorder checkbox in MR should be readonly 2026-01-10 15:35:37 +05:30
Diptanil Saha
46e7fedff8 Merge pull request #51633 from diptanilsaha/pos_settings 2026-01-10 13:42:51 +05:30
Soham Kulkarni
e8665864a4 Merge pull request #51641 from sokumon/add-standard-field 2026-01-10 01:22:18 +05:30
sokumon
b6e5b67676 chore: export sidebars for new schema 2026-01-10 00:07:12 +05:30
l0gesh29
ff9b936634 fix: add validation for return against 2026-01-09 19:08:23 +05:30
diptanilsaha
e9c009b564 fix(patch): copy the value of post_change_gl_entries from accounts settings to pos settings 2026-01-09 18:44:56 +05:30
Diptanil Saha
17955337cc Merge pull request #51618 from diptanilsaha/settings_icon 2026-01-09 18:39:49 +05:30
diptanilsaha
34fd4e7043 chore: renaming settings icon to erpnext_setting 2026-01-09 18:20:46 +05:30
diptanilsaha
bf199cc2e0 fix: renaming 'Settings' desktop icon and workspace to 'ERPNext Settings' 2026-01-09 18:20:42 +05:30
diptanilsaha
e4741072a6 fix: moved pos related accounts settings configuration to pos settings 2026-01-09 18:15:14 +05:30
Logesh Periyasamy
43d1d685c6 fix: add validation for amount and hours
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-01-09 18:11:42 +05:30
rohitwaghchaure
f23c11256c Merge pull request #51351 from rohitwaghchaure/fixed-serial-no-save-performance-issue
perf: SABB taking time to save the record
2026-01-09 17:45:36 +05:30
l0gesh29
cda8a97f4a fix: add validation for duplication 2026-01-09 17:43:55 +05:30
Rohit Waghchaure
8e143d68b4 fix: incoming rate calculation 2026-01-09 17:29:34 +05:30
Nabin Hait
79a14f49e1 Revert "chore: export sidebars for new schema" (#51631) 2026-01-09 17:24:17 +05:30
rohitwaghchaure
d3a480200e Merge branch 'develop' into fixed-serial-no-save-performance-issue 2026-01-09 16:57:24 +05:30
nivithamerlin
c1d50c492b fix(asset): remove references for composite and existing asset 2026-01-09 16:36:12 +05:30
rohitwaghchaure
d595a974ee Merge pull request #51628 from rohitwaghchaure/fixed-removed-unused-code
chore: removed unused code
2026-01-09 16:21:53 +05:30
Rohit Waghchaure
b11f20596e chore: removed unused code 2026-01-09 16:03:45 +05:30
Soham Kulkarni
c06a8568bd Merge pull request #51623 from jacob-salvi/new-icons 2026-01-09 15:08:51 +05:30
jacob-salvi
fc9bc36110 chore: update new solid icons 2026-01-09 14:53:09 +05:30
jacob-salvi
67def7dc13 chore: update new icons 2026-01-09 14:50:37 +05:30
Khushi Rawat
cdd7b12279 Merge pull request #51453 from khushi8112/payment-entry-gl-merge
fix(payment_entry): merge GL entries with similar account heads based on setting
2026-01-08 23:43:08 +05:30
Diptanil Saha
3a995ba260 Merge pull request #51613 from diptanilsaha/pos_item_selector_ui_ux 2026-01-08 23:05:36 +05:30
diptanilsaha
4d8d29b0df fix: animate on item load 2026-01-08 22:21:15 +05:30
diptanilsaha
02cefa8bdb fix: item group field clear button 2026-01-08 22:21:15 +05:30
diptanilsaha
aef2e2794b fix: race condition 2026-01-08 22:21:15 +05:30
diptanilsaha
069f28feeb fix(pos): item selector section ui/ux 2026-01-08 22:21:15 +05:30
Soham Kulkarni
62270af65b Merge pull request #51604 from sokumon/new-sidebar-schema 2026-01-08 22:13:02 +05:30
rohitwaghchaure
5139442205 Merge pull request #51607 from rohitwaghchaure/fixed-item-not-found
fix: item not found
2026-01-08 19:58:11 +05:30
Rohit Waghchaure
5eb062c065 fix: item not found 2026-01-08 19:40:18 +05:30
rohitwaghchaure
20f6e37b65 Merge pull request #51507 from elshafei-developer/fix-add-missing-translate-function
fix: add missing translation function for company default error message
2026-01-08 19:33:03 +05:30
sokumon
c5ce14dc14 chore: export sidebars for new schema 2026-01-08 18:51:01 +05:30
Soham Kulkarni
b5d0e85b59 Merge pull request #51600 from nabinhait/accounts-workspace-cleanup 2026-01-08 18:45:53 +05:30
Soham Kulkarni
7db70742e8 Merge pull request #51599 from nabinhait/financial-reports-workspace 2026-01-08 18:39:22 +05:30
Florian HENRY
bf430fce09 feat: remove old French chart of accounts with code as nex 2025 is provided 2026-01-08 13:49:16 +01:00
Florian HENRY
6bdaeb983d chore: Review PR #51595 2026-01-08 13:47:40 +01:00
Nabin Hait
b0a04e202a fix: Removed opening and closing workspace 2026-01-08 18:02:32 +05:30
Nabin Hait
721b29c8df fix: Added AR, AP, Sales and Purchase Register reports 2026-01-08 17:56:55 +05:30
ruthra kumar
f4f02458ef Merge pull request #51534 from aerele/fix/support-56421
fix(accounts): correct sales order item deletion message for MR and PO linkage
2026-01-08 17:39:21 +05:30
rohitwaghchaure
e5b93d85e6 Merge pull request #51574 from aerele/fix/support-56834
fix(stock): enable allow on submit for tracking status field
2026-01-08 16:30:02 +05:30
rohitwaghchaure
e4a0bc2d5f Merge pull request #51594 from nabinhait/debit-credit-note-links
fix: Workspace sidebar links for Debit/Credit Notes
2026-01-08 16:13:52 +05:30
Florian HENRY
c81dee137f feat: add new 2025 Charts of Accounts for France 2026-01-08 10:59:18 +01:00
Nabin Hait
8acf373e68 fix: Workspace sidebar links for Debit/Credit Notes 2026-01-08 15:23:40 +05:30
Nabin Hait
10a3f61689 fix: erpnext workspaces cleanup (#51461) 2026-01-08 15:03:11 +05:30
rohitwaghchaure
3c13543c24 Merge pull request #51514 from rohitwaghchaure/fixed-purchase-return-issue
fix: purchase return issue
2026-01-08 14:46:45 +05:30
rohitwaghchaure
3906bf450e Merge pull request #51586 from rohitwaghchaure/fixed-support-54626
fix: negative stock issue for higher precision
2026-01-08 14:36:37 +05:30
Mihir Kandoi
69597329e9 Merge pull request #51585 from mihir-kandoi/st56826 2026-01-08 14:22:36 +05:30
Rohit Waghchaure
87be020c78 fix: negative stock issue for higher precision 2026-01-08 14:11:52 +05:30
Mihir Kandoi
d0ba365aaa fix: closed WO becomes open when RM is returned 2026-01-08 14:07:14 +05:30
ruthra kumar
d82bf43684 fix: sync translations from crowdin (#51579)
fix: Swedish translations
2026-01-08 14:05:25 +05:30
Mihir Kandoi
cfa00829a8 Merge pull request #51583 from mihir-kandoi/st56954 2026-01-08 13:31:19 +05:30
Mihir Kandoi
190204a939 fix: allow all users of supplier to create purchase invoices 2026-01-08 13:14:34 +05:30
Logesh Periyasamy
bc63c85daf fix(accounting-dimension): System-generated round-off GL entries fail to set the accounting dimension (#51167)
* chore: remove disabled condition statement

* fix: add default dimension for round off gle

* fix: validate report type to handle opening entries roundoff
2026-01-08 12:05:36 +05:30
MochaMind
dbab929016 fix: Swedish translations 2026-01-08 11:26:58 +05:30
Pandiyan5273
1bfb62465f fix(stock): enable allow on submit for tracking status field 2026-01-07 19:27:16 +05:30
ruthra kumar
dd94e51d66 Merge pull request #51400 from Jatin3128/quick-entry-address-fix
fix(supplier): avoid mandatory_depends_on trigger from prefetched country in quick entry
2026-01-07 17:13:40 +05:30
ruthra kumar
fd0ed04979 Merge pull request #51294 from Jatin3128/subscription-grace-status
fix(subscription): add grace period status while invoice in grace period
2026-01-07 17:10:17 +05:30
rohitwaghchaure
f111d97444 Merge pull request #51550 from rohitwaghchaure/fixed-modified-date
fix: modified date not updated
2026-01-07 13:06:17 +05:30
ruthra kumar
ae7aa19afc Merge pull request #51192 from aerele/bank-clearance-tool
fix: add comment and validation for clearance date updation
2026-01-07 12:50:27 +05:30
ruthra kumar
772847c0d3 Merge pull request #51542 from aerele/fix/filter_in_mode_of_payment
fix(mode of payment): use valid syntax
2026-01-07 12:45:27 +05:30
ruthra kumar
ed1f1110c7 Merge pull request #51199 from Jatin3128/subscription_date_fix
fix(subscription): add cancellation and date validation
2026-01-07 12:43:42 +05:30
l0gesh29
24c8cfe128 fix: add comment and validation for clearance date updation 2026-01-07 12:36:25 +05:30
ruthra kumar
5fa9e0421c Merge pull request #51560 from frappe/l10n_develop
fix: sync translations from crowdin
2026-01-07 11:26:27 +05:30
MochaMind
103e4aaa93 fix: Bosnian translations 2026-01-07 11:06:06 +05:30
MochaMind
6729d3d1ca fix: Croatian translations 2026-01-07 11:06:03 +05:30
MochaMind
66425462ba fix: Swedish translations 2026-01-07 11:05:59 +05:30
MochaMind
e675c76628 fix: Hungarian translations 2026-01-07 11:05:56 +05:30
Matt Howard
323636b396 fix(postgres): avoid UNSIGNED cast in customer autoname 2026-01-06 15:13:30 -05:00
Matt Howard
64f391adf7 fix(postgres): compute current month sales without DATE_FORMAT 2026-01-06 15:00:25 -05:00
khushi8112
c0a85faa68 test: set up float precision 2026-01-06 23:49:21 +05:30
Diptanil Saha
57624e1e33 Merge pull request #51551 from diptanilsaha/pos_whitespace 2026-01-06 22:00:58 +05:30
Rohit Waghchaure
3acc3e6ad1 fix: modified date not updated 2026-01-06 21:05:05 +05:30
ruthra kumar
2dcba90afc Merge pull request #51528 from trustedcomputer/change-payment-references-float-to-currency
fix: change float types in payment entry reference table to currency
2026-01-06 20:50:25 +05:30
ruthra kumar
ede0dcd58a Merge pull request #51454 from Jatin3128/gh_38620
fix(Accounting Period): allow GL entries for exempted roles
2026-01-06 20:40:37 +05:30
Mihir Kandoi
a7be255261 Merge pull request #51536 from sokumon/deprecate-moduldes 2026-01-06 20:34:55 +05:30
Mihir Kandoi
015529a321 fix: change http to https 2026-01-06 20:20:25 +05:30
MochaMind
f5aaa1139c fix: sync translations from crowdin (#51404) 2026-01-06 14:55:41 +01:00
Rohit Waghchaure
d420ec0b22 fix: purchase return issue 2026-01-06 19:02:33 +05:30
Rohit Waghchaure
20320c4a6c perf: SABB taking time to save the record 2026-01-06 18:47:35 +05:30
rohitwaghchaure
e0f6f2a600 Merge pull request #51163 from aerele/mr-product-bundle
fix(material-request): get remaining qty on partial transaction with product bundle
2026-01-06 18:16:43 +05:30
ervishnucs
6cd4ef694e fix(mode of payment): use valid syntax 2026-01-06 18:13:26 +05:30
ruthra kumar
e432a88ec1 Merge pull request #51544 from ruthra-kumar/unsaved_bug_in_gain_loss_journal
fix: unsaved status on opening gain loss journal
2026-01-06 17:47:25 +05:30
ruthra kumar
8fdf6a9ac6 Merge pull request #51424 from Jatin3128/trial-balance-party-fix
fix(trial balance party): add check for parties with zero credit and debit
2026-01-06 17:47:04 +05:30
ruthra kumar
e5b02e81a9 fix: unsaved status on opening gain loss journal 2026-01-06 17:45:11 +05:30
diptanilsaha
a36331c393 fix(pos): removed white space from the bottom of pos screen 2026-01-06 16:09:48 +05:30
Khushi Rawat
b63d069635 Merge pull request #51540 from khushi8112/allow-data-import-for-asset-repiar
feat: allow data import for asset repair doctype
2026-01-06 16:08:29 +05:30
Khushi Rawat
feb4bf394a Merge pull request #51405 from khushi8112/fix-budget-variance-report
refactor: budget variance report
2026-01-06 16:05:46 +05:30
khushi8112
49f1688a51 feat: allow data import for asset repair doctype 2026-01-06 15:49:03 +05:30
sokumon
fd6683e196 fix: add deprecation message 2026-01-06 15:02:36 +05:30
khushi8112
825e3717ca fix: do not update float precision on setup 2026-01-06 14:41:36 +05:30
khushi8112
007258d657 refactor: modify test cases to handle float precision rounded to 2 decimals 2026-01-06 14:39:31 +05:30
Pandiyan5273
5a47503611 fix(accounts): correct sales order item deletion message for MR and PO linkage 2026-01-06 12:58:54 +05:30
khushi8112
07a69a073d refactor: optimize budget variance report queries 2026-01-06 12:25:14 +05:30
Sowmya
f8f82ccf31 Merge pull request #51458 from aerele/default-age-range
feat: add default-age-range in accounts settings
2026-01-06 11:07:28 +05:30
Mihir Kandoi
54cbe4222d Merge pull request #51530 from mihir-kandoi/fix-email-details-visibility 2026-01-06 10:47:03 +05:30
Mihir Kandoi
f222d3a37b fix: email details should be visible if emails are to be sent 2026-01-06 10:32:02 +05:30
trustedcomputer
8ba71300db fix: change float types in payment entry reference table to currency 2026-01-05 14:21:31 -08:00
Jatin3128
7a4cd3ac33 fix(Accounting Period): allow GL entries for exempted roles 2026-01-06 03:31:52 +05:30
khushi8112
7f6e509e20 refactor: more code cleanup 2026-01-06 01:25:56 +05:30
khushi8112
f786c16a7d refactor: better function and variable name 2026-01-06 01:05:10 +05:30
khushi8112
53b13501a9 fix: get correct total budget data 2026-01-06 00:40:32 +05:30
Navin-S-R
c84986d00e fix: calculate net profit amount from root node accounts 2026-01-05 19:09:05 +05:30
khushi8112
244319bf1d fix: show budget variance chart 2026-01-05 18:48:17 +05:30
khushi8112
f6a4f696a1 fix: Show Cumulative Amount based on checkbox in filter 2026-01-05 18:43:20 +05:30
rohitwaghchaure
afc5dda372 Merge pull request #51506 from rohitwaghchaure/fixed-reposting-for-transaction
fix: reposting fixes for transaction based and parallel reposting
2026-01-05 18:29:50 +05:30
khushi8112
8d186d6b3f fix: use correct test class 2026-01-05 16:47:32 +05:30
Khushi Rawat
140d13cfb3 Merge pull request #51504 from khushi8112/fiscal-year-not-found-error
fix: use non-standard-fieldname for budget
2026-01-05 16:44:36 +05:30
khushi8112
1296829b9c fix(test): Use the system-configured float precision 2026-01-05 16:44:06 +05:30
elshafei-developer
0950e67eea fix: add missing translation function for company default error message 2026-01-05 10:49:35 +00:00
Rohit Waghchaure
31a147126e fix: reposting fixes for transaction based and parallel reposting 2026-01-05 16:17:33 +05:30
Mihir Kandoi
72aa27a87f Merge pull request #51503 from mihir-kandoi/rfq-email-refactor 2026-01-05 15:11:14 +05:30
Mihir Kandoi
9cb5768fea refactor: RFQ email process 2026-01-05 14:50:18 +05:30
khushi8112
01c560eb99 refactor: remove budget reference from monthly distribution dashboard 2026-01-05 14:38:33 +05:30
khushi8112
fa0ac8db4d fix: use non-standard-fieldname-for-bdget 2026-01-05 14:37:38 +05:30
Diptanil Saha
57c6f4ffb6 Merge pull request #51500 from diptanilsaha/pos_ui_ux 2026-01-05 13:32:46 +05:30
diptanilsaha
84612d676b fix(pos): hide sidebar 2026-01-05 13:25:31 +05:30
Mihir Kandoi
c7a81161c7 Merge pull request #51495 from mihir-kandoi/gh51193 2026-01-05 12:36:33 +05:30
Mihir Kandoi
aac39b2671 fix: bom item code getting fg item name on row add 2026-01-05 12:32:10 +05:30
ruthra kumar
028cd97ee1 Merge pull request #51326 from aerele/fix-background-jv-submission
fix(journal entry): use submission_queue to perform submit and cancel actions for rows over 100
2026-01-05 12:21:46 +05:30
ruthra kumar
c297282fa5 Merge pull request #51457 from aerele/project-filters-jv
fix: add company filters to project
2026-01-05 10:52:40 +05:30
ruthra kumar
522e4887ca Merge pull request #51467 from aerele/pcv-account-filter
fix: update filters on period closing voucher
2026-01-05 10:49:53 +05:30
rohitwaghchaure
bd94deee62 Merge pull request #51476 from rohitwaghchaure/fixed-purchase-serial-no-return-issue
fix: not able to make purchase return for serial nos
2026-01-05 10:04:09 +05:30
Rohit Waghchaure
344572cf87 fix: not able to make purchase return for serial nos 2026-01-04 19:47:33 +05:30
rohitwaghchaure
fc9496a36b Merge pull request #51475 from rohitwaghchaure/fixed-stock-reco-cancel-issue
fix: SABB not cancelled on cancel of Stock Reco
2026-01-03 16:16:25 +05:30
Rohit Waghchaure
b204853193 fix: SABB not cancelled on cancel of Stock Reco 2026-01-03 15:59:25 +05:30
rohitwaghchaure
2d5d03e63a Merge pull request #51468 from rohitwaghchaure/fixed-support-56624
fix: not able to submit backdated stock reco
2026-01-03 15:21:22 +05:30
Rohit Waghchaure
cccd34b06a fix: not able to submit backdated stock reco 2026-01-03 15:01:30 +05:30
SowmyaArunachalam
7ab1e1f677 fix: update filters on period closing voucher 2026-01-03 14:15:03 +05:30
diptanilsaha
daabb42ad7 fix(pos): remove full screen feature 2026-01-03 11:53:40 +05:30
Mihir Kandoi
3d4c8d6d35 Merge pull request #51462 from mihir-kandoi/gh51459 2026-01-02 23:07:11 +05:30
Mihir Kandoi
247cc1d53e fix: multiple issues 2026-01-02 20:53:44 +05:30
Soham Kulkarni
2eb448d4b4 Merge pull request #51452 from sokumon/export-desktop-icons 2026-01-02 19:48:27 +05:30
Mihir Kandoi
7308021aa8 fix: use SABB posting_datetime instead of posting_date 2026-01-02 19:32:39 +05:30
rohitwaghchaure
82b49f5d9d Merge pull request #51451 from rohitwaghchaure/fixed-removed-forecasting_method
chore: removed forecasting method holt winter
2026-01-02 18:35:21 +05:30
Nabin Hait
b9e8b2808a fix: JSON decode error (#51301) 2026-01-02 18:32:52 +05:30
Mihir Kandoi
d4702ac232 Merge pull request #51455 from nishkagosalia/gh-51383 2026-01-02 17:05:58 +05:30
Nishka Gosalia
f622996c48 fix: disallowing overlapping time logs in allow on submit mode 2026-01-02 16:50:20 +05:30
khushi8112
b8b55754c8 fix: breaking test 2026-01-02 16:39:23 +05:30
khushi8112
7baa75faa5 fix: add back test record - removed for debugging 2026-01-02 16:13:54 +05:30
khushi8112
6147f9c6a3 test: add tests for merging GL entries based on Accounts Settings 2026-01-02 16:11:32 +05:30
SowmyaArunachalam
7c16db567b fix: add company filters to project 2026-01-02 16:05:36 +05:30
sokumon
83bc8744bb chore: reexport desktop icons with new schema 2026-01-02 15:42:26 +05:30
Rohit Waghchaure
fd5b84fe1a chore: removed forecasting_method holt winter 2026-01-02 15:21:01 +05:30
khushi8112
59f5ee7b63 fix(payment_entry): merge GL entries with similar account heads based on setting 2026-01-02 15:09:30 +05:30
Abdeali Chharchhodawala
4632ddc497 Merge pull request #51078 from Abdeali099/custom-financial-statement-pdf-export 2026-01-02 13:29:49 +05:30
Mihir Kandoi
7bb0ec836f Merge pull request #51441 from mihir-kandoi/semgrep-autofixes 2026-01-01 22:11:32 +05:30
Mihir Kandoi
ca568a01f5 fix: autofixes by semgrep 2026-01-01 21:56:12 +05:30
Mihir Kandoi
06fd0f8084 Merge pull request #51439 from frappe/revert-51434-semgrep-autofix 2026-01-01 18:22:07 +05:30
Mihir Kandoi
cc6cb5d1af Revert "fix: autofixes by semgrep" 2026-01-01 18:07:34 +05:30
Mihir Kandoi
6c6bf306d3 Merge pull request #51434 from mihir-kandoi/semgrep-autofix 2026-01-01 18:06:02 +05:30
rohitwaghchaure
43e859b093 Merge pull request #51436 from rohitwaghchaure/fixed-github-43596
fix: do not sum quantities for similar items across different operations
2026-01-01 17:43:24 +05:30
Rohit Waghchaure
855dd4162f fix: do not sum quantities for similar items across different operations 2026-01-01 17:20:10 +05:30
rohitwaghchaure
28a49a21ee Merge pull request #51432 from rohitwaghchaure/fixed-github-43967
fix: LCV rate not updated based on PI rate if PI created from PO
2026-01-01 16:26:08 +05:30
Rohit Waghchaure
50b30fce4a fix: LCV rate not updated based on PI rate if PI created from PO 2026-01-01 16:06:52 +05:30
Mihir Kandoi
debd3c6886 fix: autofixes by semgrep 2026-01-01 15:59:44 +05:30
Jatin3128
83ddaf1696 fix(trial balance party): add check for parties with zero credit and debit 2026-01-01 13:49:09 +05:30
Ankush Menat
f79e7dd806 build: bump required python version (#51421)
closes https://github.com/frappe/erpnext/issues/51420
2026-01-01 12:26:52 +05:30
rohitwaghchaure
9854b146a5 Merge pull request #51427 from rohitwaghchaure/fixed-github-48006
fix: avoid duplicate items under the same parent in bom creator
2026-01-01 12:25:10 +05:30
Nabin Hait
b93f920592 fix: sidebar items for accounts (#51418) 2026-01-01 12:24:39 +05:30
Rohit Waghchaure
45fe7f2875 fix: avoid duplicate items under the same parent in bom creator 2026-01-01 12:03:37 +05:30
Khushi Rawat
1fba6fc2f4 Merge pull request #51426 from khushi8112/refactor-workspace-sidebar-for-budget
refactor: remove monthly distribution doctype from sidebar
2026-01-01 11:52:10 +05:30
khushi8112
7e81be7b8c refactor: remove monthly distribution doctype from sidebar 2026-01-01 11:34:32 +05:30
Mihir Kandoi
767bb534c0 Merge pull request #51419 from mihir-kandoi/actual-qty-mat-rcpt 2025-12-31 18:07:45 +05:30
Mihir Kandoi
349db6cfaf fix: actual qty resets to 0 on save of material receipt 2025-12-31 17:52:36 +05:30
Mihir Kandoi
73e7c38bc7 Merge pull request #51414 from nishkagosalia/gh-51382 2025-12-31 15:50:54 +05:30
ruthra kumar
7811c08e2b Merge pull request #51409 from ljain112/fix-tds-manual-entry-taxes
fix(tds): always update item taxable value
2025-12-31 15:13:53 +05:30
Nishka Gosalia
aefd36e050 fix: task actual hours calculation correction post timesheet update after submit 2025-12-31 15:11:50 +05:30
Mihir Kandoi
87d00a373f Merge pull request #51305 from mihir-kandoi/gh45824 2025-12-31 09:26:38 +00:00
ljain112
86b0f67dbc fix(tds): correct tax logic for customer 2025-12-31 14:26:22 +05:30
ljain112
410a4b20af fix: always update item taxable value 2025-12-31 13:03:30 +05:30
khushi8112
f56a673baa refactor: formatted code 2025-12-31 11:40:38 +05:30
khushi8112
24757465ce fix: consider dimension filter while generating report 2025-12-31 11:40:38 +05:30
khushi8112
c57a43b3f4 fix: distribute non-monthly budgets across months when creating budget map 2025-12-31 11:40:38 +05:30
khushi8112
e3fb7f4c47 fix: include budget with for multiple fiscal years 2025-12-31 11:40:38 +05:30
khushi8112
8108fe4ca5 fix: correct query of fetching budget records 2025-12-31 11:40:37 +05:30
khushi8112
8ebd1fd029 refactor: budget variance report 2025-12-31 11:40:37 +05:30
Jatin3128
a450f7a00d fix(supplier): avoid mandatory_depends_on trigger from prefetched country in quick entry 2025-12-31 04:45:28 +05:30
Mihir Kandoi
cb5926f59b Revert "fix: purchase receipt item showing wrong expense account" (#51398) 2025-12-30 14:44:57 +00:00
rohitwaghchaure
0c08fb9303 Merge pull request #51341 from aerele/fix/remove-item-image
fix(stock): remove item image to avoid setting the image of previous item
2025-12-30 16:49:55 +05:30
rohitwaghchaure
732b04f37c Merge pull request #51375 from aerele/fix/stock-reservation-entry
fix(stock): prevent excess stock reservation
2025-12-30 16:49:18 +05:30
Navin-S-R
4adeaedfde test: validate asset split for auto created asset from purchase voucher 2025-12-30 16:29:46 +05:30
Mihir Kandoi
016170b08c Merge pull request #51255 from mihir-kandoi/read-only-job-card-employee 2025-12-30 16:08:14 +05:30
ruthra kumar
566d1a1ee2 Merge pull request #51340 from aerele/bank-reconciliation-pass-company-bank-account
fix(bank reconciliation tool): carry bank account to payment entry
2025-12-30 15:57:26 +05:30
Sudharsanan11
e1f9adf4e9 fix(stock): prevent excess stock reservation 2025-12-30 15:16:49 +05:30
Navin-S-R
23b094f151 fix(asset): handle same asset being sold in multiple line items in sales invoice 2025-12-30 14:47:28 +05:30
Khushi Rawat
ae9c9f7570 Merge pull request #51380 from khushi8112/purchase-receipt-item-expense-account-query
fix: expense_account query override in Purchase Receipt
2025-12-30 12:56:06 +05:30
Mihir Kandoi
1416ae3fb2 Merge pull request #51374 from mihir-kandoi/gh45169 2025-12-30 12:51:10 +05:30
Mihir Kandoi
c249638ebb Merge pull request #51377 from mihir-kandoi/gh47785 2025-12-30 12:31:02 +05:30
ruthra kumar
d24a68b206 Merge pull request #51356 from Jatin3128/cost-center-fix
fix(subscription): added the cost center from the subscription plan to invoice item
2025-12-30 12:30:24 +05:30
Mihir Kandoi
1097d6eef1 fix: add missing GTIN-14 barcode 2025-12-30 12:25:34 +05:30
ruthra kumar
a9b3839c3c Merge pull request #51368 from Ponnusamy1-V/error-when-reposting-on-background
fix: start reposting accounting ledger after commit
2025-12-30 12:22:56 +05:30
Mihir Kandoi
2860d5a406 fix: variant creation with translation 2025-12-30 12:14:43 +05:30
Mihir Kandoi
b8ecefa06c fix: add missing patch 2025-12-30 12:10:46 +05:30
Navin-S-R
e7e6567792 fix(asset): skip purchase document validation while splitting existing asset 2025-12-30 12:09:37 +05:30
khushi8112
292a51c160 fix: expense_account query override in Purchase Receipt 2025-12-30 12:05:48 +05:30
Ponnusamy
469a1ade79 fix: start reposting accounting ledger after commit 2025-12-30 06:33:38 +00:00
Mihir Kandoi
acb30dc6ac Merge pull request #51338 from mihir-kandoi/gh43256 2025-12-30 11:53:32 +05:30
Mihir Kandoi
43cdca950f fix: type in barcode type 2025-12-30 06:23:08 +00:00
Mihir Kandoi
e2e2e78b65 Merge pull request #51376 from mihir-kandoi/gh45649 2025-12-30 11:52:28 +05:30
Mihir Kandoi
0c43c07cf6 fix: purchase receipt item showing wrong expense account (#51362) 2025-12-30 11:51:50 +05:30
ruthra kumar
875bf5c8a9 Merge pull request #51361 from aerele/payment-entry-clear-party-name-in-internal-transfer
fix(payment entry): clear party_name for internal transfer
2025-12-30 11:47:17 +05:30
Mihir Kandoi
b30ffc561b fix: misleading and confusing item variants field copy message 2025-12-30 11:18:05 +05:30
ruthra kumar
8fa047f96d Merge pull request #51171 from nabinhait/party_merge_validation
fix: validate party's existing transaction currency before merging
2025-12-30 10:21:37 +05:30
ruthra kumar
4a30634942 Merge pull request #51165 from nabinhait/bank-clearance-ux
fix: update button was getting frozen after validation
2025-12-30 10:09:01 +05:30
Navin-S-R
9eeccb765d test: validate asset partial sales 2025-12-29 22:14:26 +05:30
Sudharsanan11
69e94248c1 fix(stock): remove item image to avoid setting the image of previous item 2025-12-29 16:16:04 +05:30
Navin-S-R
a88fe2ecab fix: refactor older testcases 2025-12-29 15:53:31 +05:30
Navin-S-R
9a2710b9d7 fix(asset): handle partial asset sales by splitting remaining quantity 2025-12-29 15:40:46 +05:30
Mihir Kandoi
8fb8f32ad6 Merge pull request #51364 from mihir-kandoi/gh51357 2025-12-29 15:32:30 +05:30
Mihir Kandoi
da899913b8 fix: RFQ does not fetch html response 2025-12-29 15:03:59 +05:30
Nishka Gosalia
41a7e64772 feat: Adding support for discarding document (#51316)
Co-authored-by: Nishka Gosalia <nishkagosalia@Nishkas-MacBook-Air.local>
2025-12-29 08:49:46 +00:00
ravibharathi656
aae0448e1f fix(payment entry): clear party_name for internal transfer 2025-12-29 12:06:28 +05:30
ruthra kumar
9f0f4d6709 Merge pull request #51358 from frappe/l10n_develop
fix: sync translations from crowdin
2025-12-29 10:52:58 +05:30
MochaMind
7b4cb629de fix: Persian translations 2025-12-29 10:05:29 +05:30
Jatin3128
3ef66f65b9 fix(subscription): added the cost center from the subscription plan to invoice item 2025-12-29 01:34:32 +05:30
MochaMind
70f1d3ca47 fix: sync translations from crowdin (#51246)
* fix: Slovenian translations

* fix: Persian translations

* fix: Serbian (Latin) translations

* fix: French translations

* fix: Spanish translations

* fix: Arabic translations

* fix: Czech translations

* fix: Danish translations

* fix: German translations

* fix: Hungarian translations

* fix: Italian translations

* fix: Dutch translations

* fix: Polish translations

* fix: Portuguese translations

* fix: Russian translations

* fix: Serbian (Cyrillic) translations

* fix: Swedish translations

* fix: Turkish translations

* fix: Chinese Simplified translations

* fix: Vietnamese translations

* fix: Portuguese, Brazilian translations

* fix: Indonesian translations

* fix: Tamil translations

* fix: Thai translations

* fix: Croatian translations

* fix: Burmese translations

* fix: Bosnian translations

* fix: Norwegian Bokmal translations

* fix: Esperanto translations

* fix: Persian translations

* fix: Serbian (Latin) translations

* fix: Serbian (Cyrillic) translations

* fix: Persian translations
2025-12-28 20:07:19 +01:00
Jatin3128
68ccb961f1 test(subscription): add auto-completion/cancellation test case 2025-12-28 21:29:44 +05:30
Jatin3128
20dc93a4b7 fix(subscription): complete subscription if no outstanding invoices 2025-12-28 21:02:21 +05:30
Mihir Kandoi
bf791ae716 Merge pull request #51349 from KerollesFathy/fix-type-error 2025-12-28 14:47:52 +05:30
KerollesFathy
44d85b0764 fix: get_item_details error 2025-12-27 18:26:32 +00:00
Mihir Kandoi
b3526599dd Merge pull request #51343 from frappe/revert-51259-gh-51233 2025-12-26 21:58:29 +05:30
Mihir Kandoi
bce4ec20e5 Merge pull request #51330 from aerele/fix/purchase-receipt-trends 2025-12-26 21:49:19 +05:30
Mihir Kandoi
30d4d53171 Revert "fix: company creation for Italy country" 2025-12-26 21:38:58 +05:30
ravibharathi656
9dfb0fdcbb fix(bank reconciliation tool): fix incorrect bank account field mapping 2025-12-26 20:41:01 +05:30
ravibharathi656
6fc9636642 fix(bank reconciliation tool): carry bank account to payment entry 2025-12-26 18:00:08 +05:30
Mihir Kandoi
db9ce998e1 test: remove invalid test case 2025-12-26 17:33:09 +05:30
Mihir Kandoi
11db07c42e fix: division in supplier score criteria throws zero division error 2025-12-26 16:33:57 +05:30
Khushi Rawat
5f4185ede9 Merge pull request #51325 from khushi8112/budget-migration-accounting-dimensions
fix: budget migration for accounting dimensions
2025-12-26 11:55:05 +05:30
Raffael Meyer
b6cb9d4799 fix: don't duplicate default income account to Item (#50413)
* fix: don't duplicate default income account to Item

Only store _Default Income Account_ in **Item** if it's different from the **Company**'s  _Default Income Account_.

Resolves #48231

* refactor: move db call out of loop

* docs: add docstring
2025-12-25 10:22:37 +01:00
Sudharsanan11
7df349844a fix(stock): remove total bar in chart view 2025-12-25 13:56:17 +05:30
ruthra kumar
73daad0fc9 Merge pull request #51222 from aerele/restore-old-records
fix: set posting time during restore
2025-12-25 10:28:00 +05:30
Navin-S-R
fa8e80c6a0 fix(journal entry): use submission_queue to perform submit and cancel actions for rows over 100 2025-12-25 10:18:33 +05:30
rohitwaghchaure
aebcd010ba Merge pull request #51322 from rohitwaghchaure/composite-index-for-serial-no
perf: composite index for serial no
2025-12-25 09:09:38 +05:30
khushi8112
8dacfdf287 fix: budget migration for accounting dimensions 2025-12-25 00:52:24 +05:30
l0gesh29
50f73a5072 fix: handle return cancellation 2025-12-24 22:09:03 +05:30
l0gesh29
ae594e81f9 test: add test for partial billing and return 2025-12-24 22:08:28 +05:30
Mihir Kandoi
0050898762 Merge pull request #50826 from Abdeali099/bulk-update-picked-qty 2025-12-24 21:34:54 +05:30
Rohit Waghchaure
734d553338 perf: composite index for serial no 2025-12-24 20:15:04 +05:30
ruthra kumar
ca31a94c76 Merge pull request #51286 from ruthra-kumar/refactor_workspace_charts
refactor: cleanup accounting workspace and charts
2025-12-24 19:54:34 +05:30
ruthra kumar
666c77dd91 refactor: accounting workspace 2025-12-24 19:34:57 +05:30
ruthra kumar
20654acdd7 refactor: update icon and chart in financial report workspace 2025-12-24 19:34:57 +05:30
ruthra kumar
e8777d4f72 refactor: remove workspace for receivable and payable 2025-12-24 19:34:57 +05:30
ruthra kumar
3ebff7e236 refactor: number cards in selling 2025-12-24 19:34:43 +05:30
ruthra kumar
f44a181c81 refactor: remove columns from chart labels 2025-12-24 19:02:59 +05:30
ruthra kumar
4a8ed972d0 refactor: subscription and budget sidebars 2025-12-24 19:02:59 +05:30
ruthra kumar
ca2ddd5d92 refactor: banking sidebar 2025-12-24 19:02:59 +05:30
ruthra kumar
201dbad545 refactor: remove redundant icons 2025-12-24 19:02:59 +05:30
ruthra kumar
c44adc6d98 refactor: add opening and closing to Accounting sidebar 2025-12-24 19:02:59 +05:30
ruthra kumar
9580a3c9f7 refactor: add charts to financial statements workspace 2025-12-24 19:02:59 +05:30
ruthra kumar
941b70a185 refactor: reorder icon in ERPNext desktop dialog 2025-12-24 19:02:59 +05:30
ruthra kumar
232cc16bbb refactor: add process payment reconciliation to sidebar 2025-12-24 19:02:56 +05:30
ruthra kumar
96829787b9 refactor: move receivables and payables outside of group 2025-12-24 19:02:32 +05:30
ruthra kumar
b55afe36c8 refactor: tidy up accounts workspace and dashboard 2025-12-24 19:02:29 +05:30
l0gesh29
57d34ab146 fix: include total hours validation in depends on 2025-12-24 18:35:48 +05:30
Mihir Kandoi
c7069df4b4 Merge pull request #51306 from mihir-kandoi/gh40171 2025-12-24 16:55:11 +05:30
rohitwaghchaure
46f8cfd228 Merge pull request #51293 from rohitwaghchaure/fixed-test-case-to-handle-complex-cases
test: test cases to handle complex cases
2025-12-24 15:34:10 +05:30
Rohit Waghchaure
dd18d78c10 test: test cases to handle complex cases 2025-12-24 15:05:35 +05:30
rohitwaghchaure
6cb6cbb00b Merge pull request #51310 from rohitwaghchaure/fixed-index-for-serial-no
perf: index for warehouse field
2025-12-24 14:59:16 +05:30
Rohit Waghchaure
23c70332df perf: index for warehouse field 2025-12-24 14:31:12 +05:30
Soham Kulkarni
278fd072c3 Merge pull request #51308 from sokumon/desktop-icons 2025-12-24 14:25:39 +05:30
sokumon
fc92458378 chore: rename corrrect desktop icon 2025-12-24 14:08:53 +05:30
Diptanil Saha
a35216a668 Merge pull request #51304 from diptanilsaha/ral_preview 2025-12-24 12:52:12 +05:30
Mihir Kandoi
175d262b94 fix: correctly copy serial/batch from cancelled doc 2025-12-24 12:45:09 +05:30
diptanilsaha
bd9f5fca08 fix(repost accounting ledger): prevent preview generation when no vouchers are selected 2025-12-24 11:52:48 +05:30
SowmyaArunachalam
f523c7889e fix: update remaining qty calculation 2025-12-23 22:02:57 +05:30
Mihir Kandoi
ca00b46507 Merge pull request #51256 from aerele/fix/validate-delivered-qty 2025-12-23 21:31:20 +05:30
SowmyaArunachalam
88dd869a11 fix(material-request): consider delivered qty for remaining qty calculation 2025-12-23 21:20:15 +05:30
Mihir Kandoi
4e04d5d1bc Merge pull request #48347 from iamkhanraheel/add-supplier_group-permission 2025-12-23 21:14:49 +05:30
SowmyaArunachalam
f2160a0629 chore: check 2nd row value 2025-12-23 21:14:06 +05:30
SowmyaArunachalam
9ca3d00eb7 test(marterial-request): validate partial transaction with product bundle 2025-12-23 21:14:06 +05:30
SowmyaArunachalam
6ade609dd6 fix(material-request): get remaining qty on partial transaction with product bundle 2025-12-23 21:14:06 +05:30
iamkhanraheel
e84c84975c feat: added supplier group field to link permission 2025-12-23 21:00:04 +05:30
Lakshit Jain
c66f78c784 feat: Introduce tax withholding entry 2025-12-23 20:47:53 +05:30
Mihir Kandoi
56192afe5f Merge pull request #51225 from aerele/report-button 2025-12-23 20:46:01 +05:30
Mihir Kandoi
8af3dd0ba0 Merge pull request #51283 from nishkagosalia/gh-49518-2 2025-12-23 20:30:32 +05:30
Mihir Kandoi
655b35fcdd Merge pull request #51263 from mihir-kandoi/gh48779 2025-12-23 20:20:39 +05:30
l0gesh29
ff0b37055b feat: add list_view status for partial billing 2025-12-23 20:14:45 +05:30
l0gesh29
c87b5d3132 feat(timesheet): handle partial billing in sales invoice 2025-12-23 20:13:54 +05:30
l0gesh29
38a4642479 feat: modify field properties 2025-12-23 20:10:38 +05:30
Jatin3128
489a035637 fix(subscription): add grace period status while invoice in grace period 2025-12-23 19:58:45 +05:30
Sudharsanan11
2073cb0106 test(manufacturing): add test to validate planned qty 2025-12-23 18:26:17 +05:30
Sudharsanan11
eda8a621c6 fix(manufacturing): validate delivered qty in production plan 2025-12-23 18:13:48 +05:30
rohitwaghchaure
83ab480211 Merge pull request #51271 from rohitwaghchaure/fixed-scrap-items-in-job-card
fix: scrap items in job card
2025-12-23 18:00:58 +05:30
ruthra kumar
f122509816 Merge pull request #51285 from diptanilsaha/frankfurter-v14-patch
fix(patch): handle currency exchange settings frankfurter api update for older versions
2025-12-23 17:40:52 +05:30
diptanilsaha
50bb1ce31d fix(patch): fallback for frankfurter settings v14 patch 2025-12-23 17:23:08 +05:30
rohitwaghchaure
2873dab98e Merge pull request #51276 from rohitwaghchaure/fixed-expense-account-stock-entry
fix: use stock adjustment if the account has not set
2025-12-23 17:07:43 +05:30
Rohit Waghchaure
9bbcbe0ac3 fix: use stock adjustment if the account has not set 2025-12-23 16:08:37 +05:30
Nishka Gosalia
8310b5ca36 feat: Added description column for update items 2025-12-23 15:47:44 +05:30
Rohit Waghchaure
09bcadfbb9 fix: scrap items in job card 2025-12-23 15:38:09 +05:30
Mihir Kandoi
250b9755f2 Merge pull request #51248 from vorasmit/phantom-as-additional-costs 2025-12-23 15:04:09 +05:30
Mihir Kandoi
6ac88f9bbf test: add test case 2025-12-23 14:57:23 +05:30
Mihir Kandoi
4b07d4a1b9 fix: create delivery notes condering customer AND addresses 2025-12-23 14:57:23 +05:30
Akhil Narang
1b530f7f92 Merge pull request #51275 from akhilnarang/dependency-update
build: update to python3.14 and nodejs 24, bump dependencies
2025-12-23 14:51:27 +05:30
Akhil Narang
8a8e1a2c4d chore: update action versions
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-12-23 13:35:13 +05:30
Akhil Narang
352c585208 build(ci): use python 3.14 and node 24
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-12-23 12:58:36 +05:30
Akhil Narang
e98d8bf638 build(deps): bump
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-12-23 12:52:03 +05:30
rohitwaghchaure
bb21415a72 Merge pull request #51259 from nishkagosalia/gh-51233
fix: company creation for Italy country
2025-12-23 12:42:41 +05:30
Nishka Gosalia
bc784a0e73 fix: company creation for Italy country 2025-12-22 20:23:03 +05:30
Mihir Kandoi
48c53c06ce Merge pull request #51266 from mihir-kandoi/fix-phantom-bom-filter 2025-12-22 19:33:03 +05:30
Mihir Kandoi
5e8d7a949c fix: filter not changing when field is changed 2025-12-22 19:31:24 +05:30
rohitwaghchaure
1d0aa4d3ec Merge pull request #51251 from rohitwaghchaure/fixed-added-limit-serial-nos
fix: limit condition to fetch serial nos
2025-12-22 17:44:58 +05:30
Khushi Rawat
f1b79e6687 Merge pull request #51262 from khushi8112/validate-finance-books-row-values
fix: validate depreciation row values
2025-12-22 17:34:18 +05:30
Rohit Waghchaure
da4b78491d fix: limit condition to fetch serial nos 2025-12-22 17:21:48 +05:30
khushi8112
16c6b2c39f fix: validate depreciation row values 2025-12-22 16:50:43 +05:30
rohitwaghchaure
9e28703ad9 Merge pull request #51258 from frappe/revert-50978-fixed-support-55110
Revert "fix: performance of the reposting"
2025-12-22 16:41:38 +05:30
rohitwaghchaure
e9c37642c8 chore: fix linters issue 2025-12-22 16:23:15 +05:30
rohitwaghchaure
d191b80587 chore: fix test case 2025-12-22 16:22:44 +05:30
rohitwaghchaure
280558efa2 Revert "fix: performance of the reposting" 2025-12-22 16:19:11 +05:30
Mihir Kandoi
cfe2181bf8 chore: make job card employee field read only 2025-12-22 15:55:54 +05:30
Mihir Kandoi
fbb67265b2 Merge pull request #51224 from aerele/task-end-date 2025-12-22 15:34:30 +05:30
Khushi Rawat
58e4f5690e Merge pull request #51186 from khushi8112/finance-book-wise-balance
fix: filter COA balances by finance book
2025-12-22 14:41:38 +05:30
Smit Vora
a441b1c53a test: ensure enough stock 2025-12-22 13:19:15 +05:30
rohitwaghchaure
0e1b45ca26 Merge pull request #51215 from vorasmit/fix-disassembly
fix: de-duplicate rows on disassembly with multiple manufacture entries
2025-12-22 11:38:35 +05:30
Smit Vora
a5e5365ba9 fix: conditional check for phantom item if field exists 2025-12-22 11:34:37 +05:30
Mihir Kandoi
dfe57b9fd3 Merge pull request #51249 from mihir-kandoi/fix-local-tests 2025-12-22 11:29:58 +05:30
Mihir Kandoi
19bfa9225d fix: flaky local test 2025-12-22 11:10:42 +05:30
Smit Vora
6e8a6582a0 fix: don't consider phantom item in additional costs in stock entry 2025-12-22 11:04:21 +05:30
rohitwaghchaure
5ae7805429 Merge pull request #51244 from rohitwaghchaure/fixed-support-55963
fix: same serial number was picked in multiple sales invoices
2025-12-22 09:38:43 +05:30
rohitwaghchaure
586431ca1c Merge pull request #51242 from aerele/fix/batch-qty-ignore-reserved-stock
fix(stock-report): ignore reserved stock in batch qty calculation
2025-12-21 22:25:52 +05:30
Rohit Waghchaure
61c31f0cd0 fix: same serial number was picked in multiple sales invoices 2025-12-21 22:22:28 +05:30
MochaMind
6c65c8955c chore: update POT file (#51240) 2025-12-21 14:27:20 +01:00
Pugazhendhi Velu
9a1f551e53 fix(stock-report): ignore reserved stock in batch qty calculation 2025-12-21 12:22:47 +00:00
SowmyaArunachalam
b8434ecfb0 test(task): validate expected end date 2025-12-19 23:27:51 +05:30
SowmyaArunachalam
c0ac5f94b5 feat: add redirect button on report 2025-12-19 22:49:34 +05:30
SowmyaArunachalam
820ccba9a4 fix(task): calculate end date if not available 2025-12-19 22:20:03 +05:30
rohitwaghchaure
c62b66038b Merge pull request #49951 from aerele/feat/update-batch-qty-report
feat(report): add batch qty update functionality in report
2025-12-19 20:25:22 +05:30
ravibharathi656
a55092d8da fix: set posting time during restore 2025-12-19 19:48:58 +05:30
Pugazhendhi Velu
15d9d8b719 fix: update batch_qty using get_batch_qty 2025-12-19 14:12:45 +00:00
Pugazhendhi Velu
cf03d03033 fix: use get_batch_qty to fetch batch data 2025-12-19 14:12:44 +00:00
Pugazhendhi Velu
9cc77934a6 refactor: fetch batch qty difference in a single db query 2025-12-19 14:12:44 +00:00
Pugazhendhi Velu
f40c492a05 feat(report): add batch qty update functionality in report 2025-12-19 14:12:44 +00:00
Smit Vora
5b3d2c0d02 test: ensure full qty reversal for items outside of BOM on disassemble 2025-12-19 19:32:34 +05:30
Smit Vora
ce123f1a89 fix: support disassemble of RMs other than in BOM 2025-12-19 19:03:22 +05:30
Smit Vora
18ac589796 test: ensure no regression after save and submit on disassemble 2025-12-19 18:19:40 +05:30
Smit Vora
df13308663 fix: don't fetch qty as it's unused 2025-12-19 18:14:06 +05:30
rohitwaghchaure
32b3a13591 Merge pull request #51219 from rohitwaghchaure/fixed-projects-home
fix: chart and number cards for projects and qc
2025-12-19 18:13:30 +05:30
rohitwaghchaure
83522bf079 Merge pull request #51214 from aerele/fix/recalculate-batch-qty
fix(stock): ignore reserved stock while calculating batch qty
2025-12-19 18:00:04 +05:30
Rohit Waghchaure
7c6f0e4463 fix: chart and number cards for projects and qc 2025-12-19 17:48:50 +05:30
Sudharsanan11
4d8ec5f54c test(stock): add test for ignore reserve stock 2025-12-19 17:25:20 +05:30
rohitwaghchaure
68fabb72fa Merge pull request #51218 from rohitwaghchaure/fixed-subcontracting-home
fix: chart and number cards for subcontracting
2025-12-19 17:21:47 +05:30
Sudharsanan11
b23c6e2687 fix(stock): ignore reserved stock while calculating batch qty 2025-12-19 17:20:17 +05:30
rohitwaghchaure
8c0dbb5bf6 Merge pull request #51217 from rohitwaghchaure/fixed-buying-home
fix: number cards for buying module
2025-12-19 17:18:37 +05:30
Rohit Waghchaure
3b221b0078 fix: chart and number cards for subcontracting 2025-12-19 16:58:29 +05:30
Rohit Waghchaure
fb0bd44a85 fix: number cards for buying module 2025-12-19 16:57:44 +05:30
Diptanil Saha
cb951bbb18 Merge pull request #51216 from diptanilsaha/gh_50890 2025-12-19 15:53:22 +05:30
diptanilsaha
0942845af7 fix(pos): prevent setting focus to item search field after item selection 2025-12-19 15:41:48 +05:30
rohitwaghchaure
b567eac01d Merge pull request #51209 from rohitwaghchaure/fixed-manufacturing-dashboard
fix: manufacturing chart and number cards
2025-12-19 15:14:32 +05:30
Rohit Waghchaure
278963be69 fix: manufacturing chart and number cards 2025-12-19 14:12:18 +05:30
Khushi Rawat
c55c97a9fe fix: add space 2025-12-19 14:11:54 +05:30
Smit Vora
a091e47bd7 fix: de-duplicate rows on disassembly with multiple manufacture entries 2025-12-19 14:07:09 +05:30
khushi8112
358083d759 fix: include entries with null finance book 2025-12-19 13:50:20 +05:30
Abdeali Chharchhoda
58cdb9503b refactor: method to get employee contact without permission check 2025-12-19 11:39:02 +05:30
Khushi Rawat
fdebf25ca7 Merge pull request #51205 from khushi8112/do-not-disable-primary-action-button
fix: do not hide primary-action for composite asset
2025-12-19 00:06:57 +05:30
Mihir Kandoi
6710a8b032 Merge pull request #51166 from mihir-kandoi/gh50060 2025-12-18 21:27:55 +05:30
Mihir Kandoi
d78a626661 chore: make mr_item indexed 2025-12-18 21:09:38 +05:30
Mihir Kandoi
48ea75c452 Merge branch 'gh50060' of https://github.com/mihir-kandoi/erpnext into gh50060 2025-12-18 21:07:57 +05:30
Abdeali Chharchhoda
ec1eb6d222 refactor: use common method to get employee contacts 2025-12-18 18:48:09 +05:30
Abdeali Chharchhoda
7b89c12470 fix: get employee email with priority if preferred is not set 2025-12-18 18:41:31 +05:30
khushi8112
e04353fc31 fix: add server side validation 2025-12-18 17:34:31 +05:30
khushi8112
e2ac7646e6 fix: set frm.has_active_capitalization before using it 2025-12-18 17:15:10 +05:30
ruthra kumar
7a3206f0bf Merge pull request #51197 from ruthra-kumar/fix_deterministic_bug_in_sql_procedures
refactor: remove custom sql function in AR SQL procedure approach
2025-12-18 17:07:24 +05:30
khushi8112
482efee6c0 fix: do not hide primary-action for composite asset 2025-12-18 16:43:26 +05:30
Mihir Kandoi
41e1f92612 Merge pull request #51204 from mihir-kandoi/gh51201 2025-12-18 16:40:24 +05:30
Mihir Kandoi
39031ae8a2 fix: customer quick entry address 2025-12-18 16:37:27 +05:30
Diptanil Saha
e53a74f2be fix(payment entry): make taxes & charges section non-collapsible and remove border (#51191) 2025-12-18 16:23:24 +05:30
Diptanil Saha
3a604dd249 fix(pegged currencies): skip adding currencies_to_add items on pegged_currency_item if source_currency or pegged_against currency doc does not exist (#51188) 2025-12-18 16:22:56 +05:30
Mihir Kandoi
11ce4ad102 Merge pull request #51202 from mihir-kandoi/gh51195 2025-12-18 16:15:01 +05:30
Mihir Kandoi
adabb6acb3 fix: NoneType error when starting job card 2025-12-18 16:06:58 +05:30
Jatin3128
00c9e20df3 fix(subscription): add cancellation and date validation 2025-12-18 16:01:08 +05:30
ruthra kumar
d1e0992253 refactor: remove custom sql function in AR SQL procedure approach 2025-12-18 15:45:05 +05:30
Khushi Rawat
260e6fb979 Merge pull request #51175 from khushi8112/asset-depreciation-manual-value-update
fix: manual depreciation update logic
2025-12-18 15:32:36 +05:30
Mihir Kandoi
a0af90f755 Merge pull request #51194 from mihir-kandoi/type-resolve-error 2025-12-18 15:18:18 +05:30
Mihir Kandoi
b0d3681593 Merge pull request #51189 from mihir-kandoi/gh49776 2025-12-18 15:11:40 +05:30
Mihir Kandoi
8b1fd7469c fix: get_item_tax_template error 2025-12-18 14:56:51 +05:30
khushi8112
f991420152 fix: Apply finance book filter conditionally 2025-12-18 14:54:37 +05:30
khushi8112
31374e1134 fix: filter account balances by default finance book in COA tree view 2025-12-18 14:54:37 +05:30
khushi8112
c186b1d9c8 fix: additional check to regenerate depreciation 2025-12-18 14:52:58 +05:30
khushi8112
178bc759c7 fix: logic error in docstatus check and use better arg 2025-12-18 14:52:58 +05:30
khushi8112
aa8e8da29f fix: check and update depreciation if depreciation method was changed 2025-12-18 14:52:58 +05:30
khushi8112
028b6790cf fix: update depreciation schedule when asset value changes for manual depreciation 2025-12-18 14:52:58 +05:30
Mihir Kandoi
333169e52b test: patch: add test case and patch 2025-12-18 09:17:42 +00:00
Mihir Kandoi
17320d1062 refactor: pick list from material request 2025-12-18 09:17:42 +00:00
Mihir Kandoi
34cee8fa9c fix: set conversion factor before validation 2025-12-18 09:16:55 +00:00
Ankush Menat
fe66bd4dc2 test: fix tests failing due to dependent state (#51187)
* test: Use fixture instead of hardcoded employee

* test: create user before assigning
2025-12-18 09:13:05 +00:00
Mihir Kandoi
9638e7ea83 Merge pull request #51168 from nishkagosalia/gh-42873 2025-12-18 10:14:05 +05:30
Mihir Kandoi
80933ec66b test: patch: add test case and patch 2025-12-17 20:16:13 +05:30
rohitwaghchaure
abef84358c Merge pull request #51022 from rohitwaghchaure/feat-run-parallel-reposting
feat: run parallel reposting
2025-12-17 16:41:40 +05:30
Nabin Hait
f48b90c600 fix: validate party's existing transaction currency before merging 2025-12-17 15:26:36 +05:30
Diptanil Saha
64e50f2e55 Merge pull request #51169 from diptanilsaha/gh_39653 2025-12-17 15:21:29 +05:30
diptanilsaha
848f8d6b1f fix(payment entry): set row id for 'On Previous Row Amount' or 'On Previous Row Total' charge type on tax table 2025-12-17 15:12:45 +05:30
Nishka Gosalia
1562e9b828 fix: Disallow due date to be before the posting date in Quotation 2025-12-17 14:44:24 +05:30
Mihir Kandoi
1778e2c564 refactor: pick list from material request 2025-12-17 14:41:09 +05:30
Nabin Hait
62acc4aeb5 fix: update button was getting freezed after validation 2025-12-17 14:36:40 +05:30
Khushi Rawat
da4d6cb567 Merge pull request #51156 from aerele/asset-depreciation-schedule-calculation
fix: show company currency in asset depreciation schedule
2025-12-17 14:33:34 +05:30
Khushi Rawat
5f80857bc9 fix: show correct PR amount in email template (#51033)
* fix: show correct PR amount in email template

* fix: add translation string
2025-12-17 14:06:54 +05:30
Diptanil Saha
a412e0b357 Merge pull request #51155 from diptanilsaha/st_55683 2025-12-17 13:53:40 +05:30
Nishka Gosalia
f4c0611cc5 feat: update item button addition for quotation (#50976)
* feat: update item button addition for quotation

* feat: update item button addition for supplier quotation

* fix: test case

---------

Co-authored-by: Nishka Gosalia <nishkagosalia@Nishkas-MacBook-Air.local>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-12-17 13:53:10 +05:30
rohitwaghchaure
dec474ef3a fix: incorrect current qty in stock reco (#51152) 2025-12-17 13:15:00 +05:30
diptanilsaha
f3142c4af6 fix: allow rename for market segment doctype 2025-12-17 12:39:34 +05:30
Ankush Menat
0b3e40b155 perf!: Avoid updating sales data on every transaction (#51151) 2025-12-17 07:04:46 +00:00
sudarshan-g
e32f898dd7 fix: show company currency in asset depreciation schedule 2025-12-17 12:09:22 +05:30
NaviN
5f6ed62c03 fix: use serial and batch bundle to fetch incoming rate (#51119) 2025-12-16 15:51:43 +01:00
Soham Kulkarni
382e4fe843 Merge pull request #51139 from sokumon/portal-redirect 2025-12-16 19:58:23 +05:30
Diptanil Saha
f07f2cfe88 Merge pull request #51120 from aerele/item-group-filter-quickentry 2025-12-16 18:26:17 +05:30
Mihir Kandoi
978aa9ac23 Merge pull request #51141 from aerele/fix/filter-disabled-suppliers 2025-12-16 18:21:23 +05:30
Mihir Kandoi
af4707d1ee Merge pull request #51136 from mihir-kandoi/gh42070 2025-12-16 18:17:51 +05:30
Mihir Kandoi
76da5efaa9 Merge pull request #51137 from mihir-kandoi/gh42599 2025-12-16 18:13:33 +05:30
sokumon
5986d79643 fix: remove /portal redirects 2025-12-16 18:12:58 +05:30
Sudharsanan11
6cc2290f6e fix(buying): add disabled filter for supplier 2025-12-16 17:58:20 +05:30
Raffael Meyer
ba9bbed038 fix(Rename Tool): use "Link" field instead of "Select" 2025-12-16 17:54:50 +05:30
Mihir Kandoi
273ff33ebf Merge pull request #51130 from mihir-kandoi/gh51126 2025-12-16 17:52:18 +05:30
Mihir Kandoi
38affb0562 fix: delayed tasks summary chart color 2025-12-16 17:47:23 +05:30
Mihir Kandoi
44840b8b80 fix: work order status for non tracked SFG 2025-12-16 17:33:41 +05:30
Kavin
2f19244660 fix(subcontract): ignore BOM qty validation for alternative items (#51122) 2025-12-16 17:26:34 +05:30
Logesh Periyasamy
890316a793 fix: add validation for transferred qty and handle MR transfer status for in-transit entry. (#50683)
* fix: add validation for transferred qty

* fix: modify if statement

* test: add unit test for mr transfer status in-transit entry
2025-12-16 17:25:30 +05:30
Mihir Kandoi
b6aec1d31a fix: ignore transfer material against if track semi finished goods 2025-12-16 16:36:48 +05:30
ruthra kumar
c2cd09cda7 Merge pull request #51048 from aerele/fetch-exchange-gain-loss-account
fix(payment entry): fetch gain loss account from company boot
2025-12-16 16:27:35 +05:30
ruthra kumar
c4136870cf Merge pull request #51123 from ruthra-kumar/fix_security_issue_in_payment_request
fix: ensure type on method parameter
2025-12-16 15:33:10 +05:30
ruthra kumar
c055e86e51 fix: ensure type on method parameter 2025-12-16 14:05:28 +05:30
Afsal Syed
3bef6bf5ef fix: add link filters for item group in quickentry 2025-12-16 13:03:31 +05:30
Mihir Kandoi
9ff5425859 Merge pull request #51100 from mihir-kandoi/gh41716 2025-12-16 12:01:37 +05:30
Mihir Kandoi
d0bdbfecef Merge pull request #51102 from mihir-kandoi/price-list-js-error 2025-12-16 11:24:28 +05:30
Mihir Kandoi
c287665033 fix: rejected qty not outward-ed in SCR 2025-12-16 11:23:19 +05:30
ruthra kumar
ac6583574e Merge pull request #51114 from frappe/l10n_develop
fix: sync translations from crowdin
2025-12-16 11:05:32 +05:30
ruthra kumar
9a4619a289 Merge pull request #51077 from aerele/standardize-cost-center-trigger
refactor: standardize cost_center updation across transactions
2025-12-16 10:51:29 +05:30
MochaMind
0563faca54 fix: Hungarian translations 2025-12-16 07:05:45 +05:30
MochaMind
bc213c45aa fix: Persian translations 2025-12-16 07:05:26 +05:30
0xD0M1M0
ad2c58cf2a feat: introduce extended bank transaction fields (#50021)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-12-15 20:27:23 +01:00
Soham Kulkarni
8ea4e2139b Merge pull request #51097 from sokumon/portal-redirect 2025-12-15 17:56:24 +05:30
ruthra kumar
9331f8ce7e Merge pull request #50837 from Jatin3128/gh_38620
feat(accounting-period): add role-based bypass for accounting period restrictions
2025-12-15 17:36:43 +05:30
MochaMind
41cf546946 fix: sync translations from crowdin (#51000) 2025-12-15 12:28:21 +01:00
ruthra kumar
6adbe7a3fd Merge pull request #50782 from vorasmit/net-gl-balances
fix: only show net balance as opening in general ledger
2025-12-15 15:29:47 +05:30
Smit Vora
af9742a3ee Merge pull request #50788 from vorasmit/cascade-available-qty 2025-12-15 14:38:22 +05:30
Mihir Kandoi
b923a6b72c Merge pull request #51098 from mihir-kandoi/fix-dispatch-addr 2025-12-15 14:24:32 +05:30
Mihir Kandoi
09a8905b2d fix: price list arg type error 2025-12-15 14:20:10 +05:30
rohitwaghchaure
766d157320 fix: manufacturing sidebar (#51101) 2025-12-15 12:19:28 +05:30
sokumon
9486d0d462 fix: add list template to all portal doctypes 2025-12-15 10:38:30 +05:30
Mihir Kandoi
d7fea5d4e1 fix: incorrect mapping of dispatch addr when creating PO from SO 2025-12-15 07:30:45 +05:30
Mihir Kandoi
ae339f69f4 Merge pull request #49139 from Anjalii-Patel/fix-employee-report-self 2025-12-15 01:55:22 +05:30
MochaMind
af4d14e820 chore: update POT file (#51095) 2025-12-14 13:34:30 +01:00
Ankush Menat
db7456eea2 fix: Short circuit guest perm checks 2025-12-14 12:10:26 +05:30
Mihir Kandoi
88acf05ba2 Merge pull request #51091 from aerele/purchase-invoice-item-default-conversion-factor 2025-12-13 14:48:40 +05:30
Mihir Kandoi
bf3b3d4c02 Merge pull request #51090 from mihir-kandoi/active-empployee-filter 2025-12-13 14:47:26 +05:30
Mihir Kandoi
9b794d5a42 Merge pull request #51083 from aerele/mr-customer-provided-status 2025-12-13 14:39:35 +05:30
Mihir Kandoi
d811a368a9 fix: add filter to employee field in Job Card Time Log 2025-12-13 14:29:21 +05:30
Mihir Kandoi
719cb42c1e Merge pull request #51050 from mihir-kandoi/gh41617 2025-12-13 14:23:54 +05:30
ravibharathi656
8d523d2cd3 fix: remove default conversion factor in purchase invoice item 2025-12-13 14:16:03 +05:30
Mihir Kandoi
3de80ec640 fix: test cases 2025-12-13 13:39:19 +05:30
Mihir Kandoi
c9eda3c279 fix: bom creator page width and rate calculation 2025-12-13 11:40:51 +05:30
rohitwaghchaure
10a66b8e73 Merge pull request #51063 from KerollesFathy/fix-get-doctypes-to-be-ignored-list
fix(transaction-deletion): Add virtual doctypes to the list of ignored doctypes
2025-12-13 07:41:06 +05:30
KerollesFathy
0f7d89f4d1 refactor: remove redundant assignment of doctypes_to_be_ignored_list 2025-12-12 20:38:02 +00:00
SowmyaArunachalam
aa8a0d0a9f test(material-request): add test to validate status 2025-12-12 22:56:17 +05:30
rohitwaghchaure
2fa948b070 Merge pull request #51079 from rohitwaghchaure/fixed-support-54205
fix: stock ageing report
2025-12-12 20:42:30 +05:30
Rohit Waghchaure
cb84ffd972 fix: stock ageing report 2025-12-12 17:36:23 +05:30
Navin-S-R
c28f6f1856 refactor: standardize cost_center updation across transactions 2025-12-12 14:25:08 +05:30
Khushi Rawat
25bf2d94e7 Merge pull request #51070 from khushi8112/budget-validation-condition
fix: validate budget after cost center allocation
2025-12-12 13:51:08 +05:30
rohitwaghchaure
2db85ad883 Merge pull request #51069 from rohitwaghchaure/fixed-github-47939
fix: incorrect invoice qty
2025-12-12 13:36:08 +05:30
rohitwaghchaure
c3723e798d Merge pull request #51034 from aerele/fix/company-address-validation
fix(accounts): handle drop ship in company linked address validation
2025-12-12 13:34:14 +05:30
Sudharsanan11
f6a96e5563 test(accounts): add validation test for dispatch address with drop ship enabled 2025-12-12 13:14:40 +05:30
Sudharsanan11
2ec119e561 fix(accounts): handle drop ship in company linked address validation 2025-12-12 13:14:33 +05:30
Khushi Rawat
a8b94f2090 Merge pull request #51041 from aerele/fix-trial-balance-roundoff
fix(trial_balance): remove hardcoded precision for currency values
2025-12-12 13:09:08 +05:30
Rohit Waghchaure
96cdb7d54f fix: incorrect invoice qty 2025-12-12 13:06:51 +05:30
khushi8112
f9be8a46fb fix: validate budget after cost center allocation 2025-12-12 12:52:04 +05:30
rohitwaghchaure
3c032e1f35 Merge pull request #51047 from aerele/fix/disassembly-stock-entry
fix(manufacturing): get items for disassembly order
2025-12-12 11:44:12 +05:30
SowmyaArunachalam
97a7c0162d fix(material-request): update customer provided status 2025-12-12 11:21:42 +05:30
KerollesFathy
45a7195abe refactor: switch to or_filters so the query hits the DB only once 2025-12-11 20:56:05 +00:00
KerollesFathy
c7a7cb2b90 fix: Add virtual doctypes to the list of ignored doctypes in transaction deletion 2025-12-11 19:45:01 +00:00
Sagar Vora
7ad123592c Merge pull request #51057 from sagarvora/remove-draft-guard
fix: re-calculate outstanding / write-off amount during submission
2025-12-11 23:04:29 +05:30
Sagar Vora
09c9ac1b66 fix: re-calculate outstanding / write-off amount during submission 2025-12-11 22:37:59 +05:30
Soham Kulkarni
9cc48f3686 Merge pull request #51055 from jacob-salvi/develop 2025-12-11 19:11:19 +05:30
jacob-salvi
5ed48a8539 chore: exporting desktop icons 2025-12-11 18:49:30 +05:30
Sagar Vora
bd595602de chore: ignore noisy commits from git blame (#51049) 2025-12-11 18:05:51 +05:30
Sagar Vora
b3fdef8d19 fix: ensure fresh grand_total_diff is used for each calculation 2025-12-11 12:18:37 +00:00
Sudharsanan11
99148a2aba fix(manufacturing): get items for disassembly order 2025-12-11 16:35:02 +05:30
Sudharsanan11
86d6facab3 fix(manufacturing): add validation for disassemble qty 2025-12-11 16:32:04 +05:30
ravibharathi656
8e54be7808 fix(payment entry): fetch gain loss account from company boot 2025-12-11 16:20:10 +05:30
Khushi Rawat
f15efc543b feat: supplier invoice date in opening invoice creation tool (#50835) 2025-12-11 16:09:22 +05:30
Diptanil Saha
d4ef2ebd3c Merge pull request #51037 from diptanilsaha/frankfurter-api-backward-compatibility 2025-12-11 15:53:48 +05:30
Rohit Waghchaure
767e4762bb feat: run parallel reposting 2025-12-11 15:47:18 +05:30
diptanilsaha
5c2bb66028 fix(currency exchange settings): added backward compatibility for frankfurter api 2025-12-11 15:14:28 +05:30
Navin-S-R
a8af04f6fc fix(trial_balance): remove hardcoded precision for currency values 2025-12-11 15:07:46 +05:30
rohitwaghchaure
605b7c5efe Merge pull request #51035 from rohitwaghchaure/fixed-serial-nos-for-putaway
fix: put-away rule not applying on serial nos
2025-12-11 14:38:19 +05:30
Mihir Kandoi
a34dd9d247 Merge pull request #50955 from aerele/issue-50951 2025-12-11 14:24:10 +05:30
Rohit Waghchaure
6bb0bdcdca fix: putaway rule not applying on serial nos 2025-12-11 13:43:58 +05:30
rohitwaghchaure
a20fd7428c Merge pull request #51030 from mihir-kandoi/gh49703
fix: update project costing based on child table field and not parent…
2025-12-11 13:22:32 +05:30
Mihir Kandoi
8d9b83e410 Merge pull request #51014 from mihir-kandoi/gh42653-3 2025-12-11 11:35:33 +05:30
Mihir Kandoi
e57d2b4811 fix: update project costing based on child table field and not parent level 2025-12-11 11:21:35 +05:30
rohitwaghchaure
f3ba59480b Merge pull request #51027 from rohitwaghchaure/fixed-support-48228
fix: Serial/Batches not fetching when creating Material Transfer from Purchase Receipt
2025-12-11 10:57:44 +05:30
Mihir Kandoi
4e4452b73e Merge pull request #51019 from mihir-kandoi/gh50781 2025-12-11 10:23:28 +05:30
Mihir Kandoi
dbbad3e6ee Merge pull request #51015 from mihir-kandoi/gh46164 2025-12-11 10:22:58 +05:30
Rohit Waghchaure
d16c50486a fix: Serial/Batches not fetching when creating Material Transfer from Purchase Receipt 2025-12-11 10:12:42 +05:30
Nishka Gosalia
254eb5e7d6 Merge pull request #51021 from nishkagosalia/gh-50390 2025-12-11 03:26:39 +00:00
Mihir Kandoi
074bdd119b fix: check if terms is not set in selling controller 2025-12-10 21:58:16 +05:30
Mihir Kandoi
3787b9fbe5 fix: check if terms is not set in buying controller 2025-12-10 21:57:33 +05:30
Mihir Kandoi
77045e45c5 fix: where condition 2025-12-10 21:54:43 +05:30
Mihir Kandoi
a52f42c7e4 fix: add is_active filter 2025-12-10 21:53:11 +05:30
Diptanil Saha
8e0227ea68 Merge pull request #50963 from Jatin3128/gh_38026 2025-12-10 17:41:23 +05:30
Diptanil Saha
c02fb7a619 Merge pull request #50967 from Jatin3128/gh_49740 2025-12-10 17:19:27 +05:30
Mihir Kandoi
0bec419301 fix: check if item is variant when creating WO from MR 2025-12-10 16:53:02 +05:30
rohitwaghchaure
4f99045ca0 Merge pull request #51007 from rohitwaghchaure/fixed-reposting-only-gl
fix: repost accounting ledgers for skipped records
2025-12-10 15:12:30 +05:30
Diptanil Saha
eb8ea4a02d Merge pull request #50948 from aerele/fix/delivery-note-trends 2025-12-10 15:11:56 +05:30
rohitwaghchaure
74f748e1f5 Merge pull request #51009 from nishkagosalia/gh-50389-2
fix: updating base amounts through python for timesheet
2025-12-10 14:43:05 +05:30
Mihir Kandoi
7f1c11c92e fix: consider reserved qty when fetching items from SO in PP 2025-12-10 14:27:52 +05:30
Mihir Kandoi
8cb0632081 fix: make sure buying/selling default terms from company master is default 2025-12-10 14:08:22 +05:30
Nishka Gosalia
e8a8d3947c fix: updating base amount in timesheet 2025-12-10 13:12:47 +05:30
Nishka Gosalia
4568114ba8 test: test cases added for checking the base amounts in timesheet 2025-12-10 13:03:14 +05:30
Nishka Gosalia
88d8310a47 fix: updating base amounts through python for timesheet 2025-12-10 13:03:14 +05:30
Khushi Rawat
85114eda7c Merge pull request #50804 from ljain112/fix-asset-repair-amount
fix: correct logic for repair cost in asset repair
2025-12-10 12:58:13 +05:30
Rohit Waghchaure
5757feb45a fix: repost accounting ledgers for skipped records 2025-12-10 12:07:46 +05:30
Khushi Rawat
f301b36b5e Merge pull request #50999 from khushi8112/budget-fixes
fix: better manual budget distribution
2025-12-10 11:57:13 +05:30
Sudharsanan11
198eb372e3 fix(stock): remove total bar in chart view 2025-12-10 11:39:55 +05:30
Mihir Kandoi
60470e2a5b Merge pull request #50952 from rtdany10/wo-flt-issue 2025-12-10 09:51:55 +05:30
Diptanil Saha
dc718f7acb Merge pull request #51001 from diptanilsaha/gh-39891 2025-12-10 09:49:44 +05:30
diptanilsaha
2fe5fad884 fix(share balance): use currency field instead of int for rate and amount 2025-12-10 08:13:22 +05:30
khushi8112
ed4c17d3a2 fix: patch to set budget distribution total 2025-12-10 03:06:05 +05:30
khushi8112
f194ac093c feat: show budget distribution total 2025-12-10 02:48:44 +05:30
khushi8112
1c82f42fa8 fix: better manual budget distribution on update 2025-12-10 02:19:29 +05:30
khushi8112
d42aad18a7 fix: remove revise budget permission 2025-12-10 00:46:03 +05:30
khushi8112
6a03fc6ede fix: add company-based filter to account field 2025-12-10 00:41:39 +05:30
khushi8112
75999a7ae4 fix: make amount and percent field read only when distribute equally is enabled 2025-12-10 00:38:33 +05:30
Diptanil Saha
b53b6cac52 Merge pull request #50944 from aerele/return-discount-validation 2025-12-09 20:46:24 +05:30
Diptanil Saha
ca12d88514 Merge pull request #50979 from ljain112/fix-item-wise-sales-register-develop 2025-12-09 20:04:13 +05:30
Mihir Kandoi
68703d6e5b Merge pull request #50930 from elshafei-developer/Fix-typo 2025-12-09 18:40:07 +05:30
Mihir Kandoi
8f2b3f8703 Merge pull request #50912 from aerele/issue-50832 2025-12-09 18:35:48 +05:30
Mihir Kandoi
997401a16a Merge pull request #50910 from aerele/fix/validate-picklist-partial-reserved-qty 2025-12-09 18:33:29 +05:30
Mihir Kandoi
a62b0ca196 Merge pull request #50984 from mihir-kandoi/phantom-bom-item-filter 2025-12-09 18:29:40 +05:30
Mihir Kandoi
2e5241a211 Merge pull request #50987 from mihir-kandoi/inward-status-fix 2025-12-09 18:29:02 +05:30
Diptanil Saha
4edcc12edc Merge pull request #50970 from Abdeali099/cancle-PI-fix 2025-12-09 17:11:31 +05:30
MochaMind
cbc0f327d6 fix: sync translations from crowdin (#50852) 2025-12-09 12:37:24 +01:00
rohitwaghchaure
30ae3e0b59 Merge pull request #50978 from rohitwaghchaure/fixed-support-55110
fix: performance of the reposting
2025-12-09 16:35:03 +05:30
Diptanil Saha
a28d799c09 Merge pull request #50968 from Abdeali099/pr-bulk-update 2025-12-09 16:28:31 +05:30
Mihir Kandoi
ec3cd7e09c feat: add return status 2025-12-09 16:15:58 +05:30
Diptanil Saha
13d4f5ec31 Merge pull request #49963 from ljain112/tds-jv 2025-12-09 15:41:35 +05:30
Mihir Kandoi
baf884f95c fix: item should not be a fixed asset if bom is phantom 2025-12-09 15:38:46 +05:30
Rohit Waghchaure
1bcfad8eb1 fix: performance of the reposting 2025-12-09 14:51:32 +05:30
ljain112
2767cb04fb fix: handle duplicate description in item-wise report 2025-12-09 13:34:14 +05:30
rohitwaghchaure
5401843315 Merge pull request #50974 from rohitwaghchaure/fixed-sre-for-return-components
fix: SRE while returning components
2025-12-09 11:44:40 +05:30
Rohit Waghchaure
5e2d246931 fix: SRE while returning components 2025-12-08 22:41:07 +05:30
rohitwaghchaure
e8f87927e1 Merge pull request #50972 from rohitwaghchaure/fixed-github-50917
fix: incorrect condition
2025-12-08 20:08:51 +05:30
rohitwaghchaure
107fa1d605 Merge pull request #50971 from rohitwaghchaure/fixed-github-33408
fix: do not create a Purchase Receipt for returned items (Debit Note)
2025-12-08 19:53:50 +05:30
Rohit Waghchaure
264baf34f6 fix: incorrect condition 2025-12-08 19:50:26 +05:30
Rohit Waghchaure
66407d22fc fix: do not create a Purchase Receipt for returned items (Debit Note) 2025-12-08 19:24:57 +05:30
Abdeali Chharchhoda
f26ee9e546 fix: ensure payment request button only shows for submitted invoices 2025-12-08 18:51:21 +05:30
Abdeali Chharchhoda
5154fa8259 refactor: payment request status updates with bulk database operation 2025-12-08 18:00:29 +05:30
Jatin3128
b2feb5abbe feat(sales_order, purchase_order): add bulk close and reopen actions in list view 2025-12-08 17:28:25 +05:30
Soham Kulkarni
b58c4edd5d chore: move report to the correct module (#50959) 2025-12-08 14:55:27 +05:30
rohitwaghchaure
c5bbabac4b Merge pull request #50962 from rohitwaghchaure/fixed-github-50829
fix: warning message to avoid serial no series overlap issue
2025-12-08 14:40:10 +05:30
Jatin3128
6ac76bac6e fix(payment_entry): add due date to payment reference rows 2025-12-08 14:17:32 +05:30
Rohit Waghchaure
c5fdc256fa fix: warning message to avoid serial no series overlap issue 2025-12-08 14:17:02 +05:30
MochaMind
0376bba345 chore: update POT file (#50957) 2025-12-07 16:32:50 +01:00
rohitwaghchaure
2e803e6494 Merge pull request #50937 from aerele/support-54673
fix: validate available stock with multiple dimensions
2025-12-07 10:46:38 +05:30
Afsal Syed
f13022835a fix: sanitize address display by stripping HTML tags using html2text 2025-12-06 21:41:33 +05:30
venkat102
6e44951a96 fix: use separate item 2025-12-06 14:00:51 +05:30
Dany Robert
80730908c9 fix: precision issue on job card submission 2025-12-06 10:49:15 +05:30
venkat102
66f56eea33 fix: enable validate_negative_stock in existing dimensions 2025-12-06 00:24:20 +05:30
Diptanil Saha
31d55248e4 Merge pull request #50931 from diptanilsaha/gh-49357 2025-12-05 16:31:54 +05:30
Diptanil Saha
994dd425d4 Merge pull request #50943 from diptanilsaha/gh-49427 2025-12-05 16:31:08 +05:30
ravibharathi656
fab1ef5d76 fix: include return invoice discount in discount validation 2025-12-05 15:40:34 +05:30
Khushi Rawat
97711b7e83 Merge pull request #50941 from khushi8112/asset-over-creation-from-pr-pi
fix(asset): prevent creating assets beyond purchased quantity
2025-12-05 15:11:24 +05:30
khushi8112
7012345968 fix: filter out cancelled asset in the query 2025-12-05 13:08:29 +05:30
khushi8112
7bfcdb13b1 fix: better validation message 2025-12-05 12:55:41 +05:30
diptanilsaha
d6bdbfe266 fix(sales invoice): 100% additional discount gl issue with discount accounting 2025-12-05 12:50:28 +05:30
khushi8112
2db09b3840 fix(asset): prevent creating assets beyond purchased quantity 2025-12-05 09:59:05 +05:30
venkat102
1e2c56874f test: validate negative stock with multiple inventory dimensions 2025-12-04 23:58:25 +05:30
rohitwaghchaure
17ff48ab92 Merge pull request #50929 from rohitwaghchaure/fixed-backflushed-basedd-on-for-job-card
fix: backflush based on for job card
2025-12-04 23:11:35 +05:30
Rohit Waghchaure
c807a7be7b fix: backflush based on for job card 2025-12-04 22:53:30 +05:30
rohitwaghchaure
44919be5a6 Merge pull request #50936 from rohitwaghchaure/fixed-stock-resevation-job-card-manufacture
fix: SRE for manufacture entry for job card
2025-12-04 22:49:51 +05:30
venkat102
8f86c1b3e9 fix: validate available stock with multiple dimensions 2025-12-04 22:05:21 +05:30
Rohit Waghchaure
4ad8e55d06 fix: SRE for manufacture entry for job card 2025-12-04 22:03:54 +05:30
El-Shafei H.
b5ad4acc41 Fix typo in docstring for update_product_bundle_rate 2025-12-04 12:03:41 +03:00
Khushi Rawat
931f0663b1 Merge pull request #50927 from khushi8112/income-as-root-type-for-round-off-account
feat: allow income as root type for round off account
2025-12-04 13:14:12 +05:30
khushi8112
2bdcec0a7e feat: allow income as root type for round off account 2025-12-04 13:03:57 +05:30
Khushi Rawat
eb38accb33 Merge pull request #50824 from khushi8112/move-accounts-freezing-setting-to-company
refactor: Move accounts freezing setting to company
2025-12-04 12:16:46 +05:30
Khushi Rawat
c5b1af84eb Merge pull request #50879 from aerele/is-fixed-asset-set-only-once
fix: remove set_only_once from is_fixed_asset field
2025-12-04 12:14:19 +05:30
Khushi Rawat
01c14b5ce4 Merge pull request #50923 from khushi8112/fix-manual-depreciation-reset-issue
fix: do not recalculate depreciation if already exist
2025-12-04 11:56:45 +05:30
Khushi Rawat
a7155c1fdb Merge pull request #50746 from aerele/asset-depreciation-role
feat(asset): make asset depreciation failure notification role configurable
2025-12-04 11:41:06 +05:30
khushi8112
b75e7a1188 fix: do not recalculate depreciation if already exist 2025-12-04 11:37:05 +05:30
rohitwaghchaure
d6087e5d92 Merge pull request #50913 from rohitwaghchaure/fixed-github-46923
fix: variant items not fetched while making BOM for Variant Item
2025-12-04 10:07:22 +05:30
ravibharathi656
70521fb9bf fix: remove set_only_once from is_fixed_asset 2025-12-04 09:17:32 +05:30
SowmyaArunachalam
b5ee193566 chore(asset): change field name 2025-12-03 20:53:38 +05:30
Rohit Waghchaure
a0256bd798 fix: variant items not fetched while making BOM for Variant Item 2025-12-03 19:24:43 +05:30
Pugazhendhi Velu
445a255a7f test: add test for return status in delivery note 2025-12-03 13:19:59 +00:00
Pugazhendhi Velu
af212f520d fix: change is_return value in filter from Yes to 1 2025-12-03 13:14:39 +00:00
Pugazhendhi Velu
dec67eecad fix: add return status for delivery note 2025-12-03 13:10:22 +00:00
rohitwaghchaure
6515bb04bf Merge pull request #50905 from rohitwaghchaure/fixed-github-46855
fix: LCV is not changing the valuation of the repacked item
2025-12-03 18:27:18 +05:30
rohitwaghchaure
b96b7bd046 Merge pull request #50853 from mihir-kandoi/gh33087
fix: incorrect putaway rule validation on stock reco
2025-12-03 18:26:46 +05:30
Sudharsanan11
758553b9fc test(picklist): add test for reserved qty after partial delivery 2025-12-03 18:23:32 +05:30
Sudharsanan11
f5b75b27d7 fix(picklist): calculate picked qty excluding the delivered qty 2025-12-03 18:23:32 +05:30
Mihir Kandoi
7c1a947cd8 Merge pull request #50906 from mihir-kandoi/gh39459 2025-12-03 18:09:37 +05:30
Rohit Waghchaure
ccbbc60585 fix: LCV is not changing the valuation of the repacked item 2025-12-03 18:07:45 +05:30
rohitwaghchaure
47af1cec1b Merge pull request #50902 from mihir-kandoi/gh39358
fix: fg qty uom in manufacture entry
2025-12-03 18:05:31 +05:30
Mihir Kandoi
ec06f4a71b fix: untranslated string in job card 2025-12-03 17:51:06 +05:30
Mihir Kandoi
d9a377108c fix: fg qty uom in manufacture entry 2025-12-03 15:45:56 +05:30
rohitwaghchaure
ed73bd6626 Merge pull request #50896 from rohitwaghchaure/fixed-github-50892
fix: quality inspection showing Not Saved
2025-12-03 13:38:15 +05:30
Rohit Waghchaure
3f78d6afed fix: quality inspection showing Not Saved 2025-12-03 13:00:06 +05:30
Khushi Rawat
4e578c4f83 fix: conflicts 2025-12-03 11:47:04 +05:30
khushi8112
404e68bdc2 fix: more patch related changes 2025-12-03 11:45:01 +05:30
khushi8112
4f33ee01cf fix: undo incorrect patch modification 2025-12-03 11:45:01 +05:30
khushi8112
c7e7e02b5b refactor: use Singles table to get acc_frozen_upto and modifier during migration 2025-12-03 11:45:01 +05:30
khushi8112
4df20a3122 fix: patch to migrate setting 2025-12-03 11:45:01 +05:30
khushi8112
29048c3364 refactor: fix incorrect conditon 2025-12-03 11:45:01 +05:30
khushi8112
09cdb943ec fix: remove duplicate method 2025-12-03 11:45:01 +05:30
khushi8112
0373f7f33f chore: validation for none type object 2025-12-03 11:45:01 +05:30
khushi8112
95877e73f0 fix: use correct date value 2025-12-03 11:45:01 +05:30
khushi8112
16f4e12854 chore: fix typo 2025-12-03 11:45:01 +05:30
khushi8112
9cc8a42074 fix: use correct field name 2025-12-03 11:45:01 +05:30
khushi8112
28febc69e8 fix: validate pending reposting till acc frozen date 2025-12-03 11:45:01 +05:30
Khushi Rawat
eee78766cd chore: resolved conflicts 2025-12-03 11:45:01 +05:30
Khushi Rawat
826c74eb71 chore: remove debug flag accidentally left in code 2025-12-03 11:45:01 +05:30
Khushi Rawat
6da10b9f97 fix: update validation and test cases 2025-12-03 11:45:01 +05:30
Khushi Rawat
cd540ab4cc chore: migration patch for account freezing fields 2025-12-03 11:45:01 +05:30
Khushi Rawat
b2e4e76b97 refactor: remove accounts freezing settings from accounts settings 2025-12-03 11:45:01 +05:30
Khushi Rawat
479e412a44 refactor: get frozen accounts settings from Company in tests 2025-12-03 11:45:01 +05:30
Khushi Rawat
d330700f39 refactor: get frozen accounts settings from Company in patches 2025-12-03 11:44:59 +05:30
Khushi Rawat
dc85babb4d refactor: get frozen accounts settings from Company in Deferred Revenue 2025-12-03 11:42:48 +05:30
Khushi Rawat
17a6392407 refactor: updated logic in depreciation and gl to validate acc frozen date company wise 2025-12-03 11:42:48 +05:30
Khushi Rawat
58db596027 feat: move frozen account settings to Company for company-specific configuration 2025-12-03 11:42:48 +05:30
ruthra kumar
7bca3bbcd8 Merge pull request #50875 from ruthra-kumar/ci_fix_broken_coverage
ci: fix coverage
2025-12-03 11:09:22 +05:30
Mihir Kandoi
b12b40f373 Merge pull request #50869 from mihir-kandoi/gh31117 2025-12-02 22:00:17 +05:30
rohitwaghchaure
72edd86561 Merge pull request #50888 from rohitwaghchaure/fixed-github-48282
fix: cost center not reset
2025-12-02 21:59:58 +05:30
Rohit Waghchaure
29f2ecbd6f fix: cost center not reset 2025-12-02 21:49:03 +05:30
rohitwaghchaure
8053303378 Merge pull request #50882 from rohitwaghchaure/fixed-mandatory-depends-on-for-rejected-inventory-dimension
fix: mandatory depends on for the rejected inventory dimension field
2025-12-02 20:39:59 +05:30
rohitwaghchaure
1a920c035b Merge pull request #50880 from rohitwaghchaure/fixed-sre-status-condition
fix: SRE validation
2025-12-02 20:36:33 +05:30
Diptanil Saha
f106d0e762 Merge pull request #50864 from aerele/allow-leaf-nodes-only 2025-12-02 20:36:04 +05:30
Rohit Waghchaure
5daa625fe8 fix: mandatory depends on for the rejected inventory dimension field 2025-12-02 20:21:07 +05:30
Diptanil Saha
9f599ee52d Merge pull request #50372 from aerele/validate-company-linked-address-field 2025-12-02 20:14:06 +05:30
Rohit Waghchaure
d82464b2f9 fix: SRE validation 2025-12-02 20:04:42 +05:30
ruthra kumar
88e94aa53a ci: code coverage action 2025-12-02 19:34:46 +05:30
rohitwaghchaure
95ff1d48ab Merge pull request #50874 from rohitwaghchaure/fixed-ux-material-request
fix: UX for auto created material request via reorder
2025-12-02 17:55:03 +05:30
rohitwaghchaure
3c6369d396 Merge pull request #50808 from aerele/support-51284
fix(stock entry): use fg item expense account for direct manufacturing entry
2025-12-02 17:31:54 +05:30
rohitwaghchaure
bee0e5d8d4 Merge pull request #50850 from aerele/support-53932
fix(barcode_scanner): set serial and batch before item to prevent FIFO override
2025-12-02 17:31:25 +05:30
Rohit Waghchaure
1e60076ade fix: UX for auto created material request via reorder 2025-12-02 17:21:50 +05:30
Diptanil Saha
4ad624be9c Merge pull request #50773 from aerele/delet-invoice-with-cancelled-repost 2025-12-02 17:12:19 +05:30
Diptanil Saha
026487dce7 Merge pull request #50846 from aerele/validate-product-bundle-stock-in-pos 2025-12-02 17:07:00 +05:30
Mihir Kandoi
9b5d215a7a fix: do cancellation procedures on WO close 2025-12-02 15:36:17 +05:30
Sudharsanan11
2612152456 test(pos): add test for product bundle negative stock validation 2025-12-02 15:19:08 +05:30
Smit Vora
75839f36ba Merge pull request #50733 from vorasmit/qb-changes 2025-12-02 14:41:58 +05:30
ravibharathi656
e08805128b fix: exclude is_group records 2025-12-02 14:09:12 +05:30
Smit Vora
09325aad3d refactor: tax witholding in JV out of document class 2025-12-02 14:08:05 +05:30
Mihir Kandoi
ba94d02cb4 Merge pull request #50834 from mihir-kandoi/pp-flaky-test 2025-12-02 13:37:11 +05:30
Mihir Kandoi
c404e3b093 fix: incorrect query/function logic 2025-12-02 13:19:26 +05:30
rohitwaghchaure
cd5b913750 Merge pull request #50856 from rohitwaghchaure/fixed-operation-read-only
fix: not able to set operation in work order
2025-12-02 12:22:46 +05:30
Khushi Rawat
dcdafc79ee Merge pull request #50794 from aerele/fix-capitalized-asset-repair-gl
fix: use asset in against_voucher while posting gl entries for capitalised asset repairs
2025-12-02 12:07:04 +05:30
Diptanil Saha
91e285efd7 Merge pull request #50149 from Dharanidharan2813/fix/payment-terms-template-fetching 2025-12-02 12:05:12 +05:30
Khushi Rawat
e9e498cbd0 Merge pull request #50793 from ljain112/fix-asset-repair-accounting-dimensions
fix: include accounting dimensions in stock entries created during asset repair.
2025-12-02 11:56:35 +05:30
Rohit Waghchaure
b24c38f332 fix: not able to set operation in work order 2025-12-02 11:18:07 +05:30
Mihir Kandoi
b4fbda4da3 Merge pull request #50854 from mihir-kandoi/employee-filter 2025-12-02 11:17:26 +05:30
Mihir Kandoi
21ec4ed911 fix: show only active employees when starting job card 2025-12-02 11:16:01 +05:30
Mihir Kandoi
7deb407206 fix: incorrect putaway rule validation on stock reco 2025-12-02 10:42:28 +05:30
Navin-S-R
8c35a6ecdd chore: reload asset doc before assertEqual 2025-12-02 00:43:15 +05:30
Pugazhendhi Velu
92ec633a5c fix(barcode_scanner): set serial and batch before item to prevent FIFO override 2025-12-01 19:11:57 +00:00
Navin-S-R
bcf6deec9a test: add unit test to validate capitalized asset repair gl entries being booked against the asset 2025-12-01 23:48:14 +05:30
MochaMind
35379294c3 fix: sync translations from crowdin (#50723) 2025-12-01 16:58:29 +01:00
rohitwaghchaure
1252fed642 Merge pull request #50844 from rohitwaghchaure/fixed-label-for-warehouse
fix: label for warehouse based on material request type
2025-12-01 21:28:21 +05:30
Rohit Waghchaure
699e9b4452 fix: label for warehouse based on material request type 2025-12-01 20:31:14 +05:30
Sudharsanan11
38b4536300 fix(pos): add negative stock validation for product bundle 2025-12-01 19:23:35 +05:30
Jatin3128
cfdbeb6a1a feat(accounting-period): add role-based bypass for accounting period restrictions 2025-12-01 17:33:49 +05:30
ljain112
e1fd90f731 chore: remove unused import for depreciation schedule 2025-12-01 17:06:42 +05:30
ljain112
8ee2cbf259 chore: remove unwanted strings 2025-12-01 17:05:00 +05:30
Mihir Kandoi
25458d6ba6 fix: flaky production plan test 2025-12-01 16:04:05 +05:30
diptanilsaha
1966584804 chore: trigger GitHub actions 2025-12-01 15:27:30 +05:30
ljain112
2a0ba84f69 refactor: linters 2025-12-01 13:17:47 +05:30
ljain112
0c1df30771 fix: add permission check 2025-12-01 13:13:55 +05:30
Abdeali Chharchhoda
5f986e4032 refactor: optimize picked quantity updates using bulk_update 2025-12-01 13:12:23 +05:30
Diptanil Saha
b2ddef8340 Merge pull request #50797 from aerele/dr-cr-outstanding 2025-12-01 13:11:02 +05:30
Diptanil Saha
150c764205 Merge pull request #50814 from diptanilsaha/st54148 2025-12-01 11:45:50 +05:30
rohitwaghchaure
b855eb54b3 Merge pull request #50799 from rohitwaghchaure/fixed-negative-batch-qty-in-SCR
fix: negative batch in subcontracting receipt
2025-12-01 11:44:41 +05:30
diptanilsaha
7e8d19b0c8 fix(email campaign): send emails using bcc 2025-11-30 23:30:15 +05:30
Khushi Rawat
48783d136f Merge pull request #50749 from khushi8112/replace-use-of-publish-realtime-before-print
refactor: company details popup
2025-11-30 22:12:07 +05:30
Khushi Rawat
8b8f569da2 Merge pull request #50811 from khushi8112/extend-print-format-to-other-docs
feat: Standard Print Format for Purchase Order, Delivery Note and POS Invoice
2025-11-30 21:51:52 +05:30
khushi8112
d050cd221d fix: minor change 2025-11-30 21:29:26 +05:30
khushi8112
5cfd7ec32a refactor: generalize popup for multiple doctypes 2025-11-30 18:30:47 +05:30
khushi8112
b808a51d8f refactor: Make labels translatable 2025-11-30 17:38:50 +05:30
khushi8112
1125f96316 feat: print format with images for PO, Delivery Note and POS Invoice 2025-11-30 17:25:02 +05:30
khushi8112
725a4fcf2d feat: standard print format for POS Invoice 2025-11-30 17:00:55 +05:30
MochaMind
32cf6148aa chore: update POT file (#50810) 2025-11-30 11:24:24 +01:00
Pugazhendhi Velu
ba2411b4ee test: add test for fg item expense account in direct manufacturing 2025-11-30 06:32:35 +00:00
Pugazhendhi Velu
ce1312764f fix(stock entry): use fg item expense account for direct manufacturing entry 2025-11-30 06:32:27 +00:00
khushi8112
3a4c1a9f9a refactor: Replace use publish_realtime with msgprint for cleaner flow 2025-11-30 01:26:02 +05:30
khushi8112
945390502e feat: standard print format for Purchase Order and Delivery Note 2025-11-30 01:25:11 +05:30
ruthra kumar
ae7db7ea5a Merge pull request #50802 from Jatin3128/gh_48028
fix(accounts-payable-summary): add Show GL Balance check similar to A…
2025-11-29 11:34:05 +05:30
Jatin3128
8a7e5d0626 fix(accounts-payable-summary): add Show GL Balance check similar to Accounts Receivable Summary 2025-11-28 21:41:32 +05:30
ljain112
ff9b392024 fix: add duplicate purchase invoice validation in asset repair 2025-11-28 19:13:31 +05:30
ljain112
0b84d11600 perf: enhance validation for purchase invoices to check submission status for all invoices 2025-11-28 19:09:54 +05:30
ljain112
c2810ea799 perf: replace get_doc with get_lazy_doc for asset retrieval and optimize stock entry fetching 2025-11-28 18:54:37 +05:30
ljain112
00ffdee928 fix: update repair cost logic to set value only for positive amounts 2025-11-28 18:45:07 +05:30
ljain112
b9aaae6343 fix: remove unnecessary filtering by search text in get_expense_accounts 2025-11-28 18:36:13 +05:30
ljain112
e6160d1b63 fix: correct logic for repair cost in asset repair 2025-11-28 18:30:51 +05:30
Khushi Rawat
82b6326e0b Merge pull request #50792 from ljain112/fix-asset-repair-connection
fix: add Stock Entry link to Asset Repair doctype.
2025-11-28 17:34:38 +05:30
Khushi Rawat
e42b751dce Merge pull request #50772 from aerele/fix-asset-purchase-date
fix: use posting_date instead of bill_date from purchase invoice
2025-11-28 17:18:13 +05:30
Rohit Waghchaure
71e46b3ef5 fix: negative batch in subcontracting receipt 2025-11-28 16:26:55 +05:30
l0gesh29
765f9a9bbf fix(payment-recon): add validation for outstanding of dr_cr 2025-11-28 15:01:50 +05:30
ljain112
cdbe8b909b refactor: show_general ledger for consistency with other doctyoes 2025-11-28 14:13:24 +05:30
ruthra kumar
956e5b1b68 fix: incorrect positional param for get_field_precision util (#50764)
fix: incorrect positional param for get_field_precision util
2025-11-28 14:01:36 +05:30
Smit Vora
92fdec9b92 test: add test for projected quantity cascading across multiple sales orders 2025-11-28 13:45:20 +05:30
ljain112
147a5ee953 fix: include accounting dimensions in stock entries created during asset repair. 2025-11-28 13:35:47 +05:30
Smit Vora
a2fadd9347 fix: use ValueWrapper consistently 2025-11-28 13:33:24 +05:30
ljain112
da7f28a3c3 fix: add Stock Entry link to Asset Repair doctype. 2025-11-28 13:05:24 +05:30
Navin S R
a7e43eddad fix: use asset in against_voucher while posting gl entries for capitalized asset repairs 2025-11-28 12:54:27 +05:30
Diptanil Saha
0520ab3c66 Merge pull request #50642 from Jatin3128/gh_48202 2025-11-28 11:50:34 +05:30
Smit Vora
d344be32a0 fix: cascade projected quantity across multiple items in material requests 2025-11-28 11:37:25 +05:30
Aadhil
9145bf5563 fix: restore missing account number for Indirect Expenses in standard COA with Numbers (#50767) 2025-11-27 19:23:02 +05:30
Smit Vora
b7c7e0746e fix: only show net gl balance as opening in general ledger 2025-11-27 19:15:46 +05:30
Aadhil
355aa52cb8 feat: add Account Category field to Account (Chart of Accounts) (#50766) 2025-11-27 18:40:20 +05:30
Mihir Kandoi
cf449d8dcb Merge pull request #50777 from mihir-kandoi/sr-better-uiux 2025-11-27 17:10:45 +05:30
Diptanil Saha
98e864bea0 Merge pull request #50775 from frappe/mergify/bp/develop/pr-50558 2025-11-27 16:58:42 +05:30
Mihir Kandoi
aab7cd1ae6 chore: make unnecessary field read only and show only when required 2025-11-27 16:51:35 +05:30
Diptanil Saha
31142b2f47 chore: resolve conflict 2025-11-27 16:40:55 +05:30
Sherin KR
c5d92d7999 fix: item price not considering based on valid_upto
(cherry picked from commit dfda8e6241)

# Conflicts:
#	erpnext/selling/page/point_of_sale/point_of_sale.py
2025-11-27 11:04:59 +00:00
l0gesh29
d8fc369e38 fix: add validation for cancelled reposting entries 2025-11-27 14:19:32 +05:30
Navin S R
145d40dec8 fix: use posting_date instead of bill_date from purchase invoice 2025-11-27 14:06:43 +05:30
ljain112
6079bee3a3 fix: remove redundant party variable assignments 2025-11-27 13:44:28 +05:30
ljain112
6a66ce5a97 fix: add is_tax_withholding_account field to JournalEntryAccount to avoid recursive tds 2025-11-27 13:40:23 +05:30
rohitwaghchaure
8a5fd5fe89 Merge pull request #50769 from rohitwaghchaure/fixed-two-primary-buttons
fix: two primary buttons
2025-11-27 11:56:00 +05:30
Rohit Waghchaure
f68515210b fix: two primary buttons 2025-11-27 11:43:21 +05:30
Hussain Nagaria
c2358c6b3f fix: incorrect positional param for get_field_precision util 2025-11-26 22:19:50 +05:30
rohitwaghchaure
8ed9ee9213 Merge pull request #50742 from rohitwaghchaure/fix-serial-batch-disassembly
fix: inward same serial / batches in disassembly which were used
2025-11-26 20:57:13 +05:30
Rohit Waghchaure
95e6c72539 fix: inward same serial / batches in disassembly which were used 2025-11-26 18:01:55 +05:30
Mihir Kandoi
2b3bdfe387 Merge pull request #50759 from mihir-kandoi/fix-bad-test 2025-11-26 17:59:55 +05:30
Mihir Kandoi
5391ca2a55 test: fix flaky test case 2025-11-26 17:40:05 +05:30
Jatin3128
0e7f75f5c0 fix(journal-entry): auto-populate bank account when user selects account (#50744)
* fix(journal-entry): auto-populate bank account when user selects account

* refactor(journal-entry): simplify get_value call and return None by default

---------

Co-authored-by: Jatin3128 <jatinsarna8@gmail.com>
2025-11-26 17:26:33 +05:30
Mihir Kandoi
d17120909c Merge pull request #50736 from mihir-kandoi/refactor-so-to-po 2025-11-26 16:55:12 +05:30
Soham Kulkarni
74a5325a95 Merge pull request #50732 from sokumon/export-all-sidebars 2025-11-26 16:22:55 +05:30
Diptanil Saha
0ca6e19a85 Merge pull request #50752 from ljain112/fix-round-off-so 2025-11-26 14:31:53 +05:30
Diptanil Saha
383c33b02a Merge pull request #50753 from diptanilsaha/gh-50083 2025-11-26 14:27:30 +05:30
diptanilsaha
079218ffbf fix: exchange_rate field visibility on invoice currency change 2025-11-26 14:20:05 +05:30
ljain112
563c2998ca fix: enhance SalesOrderController setup method to call super.setup 2025-11-26 12:51:14 +05:30
SowmyaArunachalam
d52d98666f chore: update description 2025-11-25 23:05:59 +05:30
SowmyaArunachalam
e830cca886 feat(asset): make asset depreciation failure notification role configurable 2025-11-25 22:28:26 +05:30
Mihir Kandoi
7b592d8737 feat: add provision to mass select supplier 2025-11-25 20:54:24 +05:30
Mihir Kandoi
88b262abc7 fix: more coderabbit issues 2025-11-25 16:07:35 +05:30
rohitwaghchaure
a8d3e9bacc Merge pull request #50735 from rohitwaghchaure/fixed-validation-for-batch
fix: stock reservation validation
2025-11-25 15:14:55 +05:30
ruthra kumar
264dcf8539 Merge pull request #50734 from diptanilsaha/frankfurter-api
chore: switched frankfurter domain from frankfurter.app to frankfurter.dev
2025-11-25 14:58:12 +05:30
diptanilsaha
f1f68ead7d chore: switched frankfurter api domain from api.frankfurter.app to api.frankfurter.dev 2025-11-25 13:10:48 +05:30
Mihir Kandoi
5a17dd8d6d fix: addresses not being carried forward 2025-11-25 12:58:52 +05:30
Mihir Kandoi
c93dba2895 fix: coderabbit suggestions 2025-11-25 12:33:51 +05:30
Mihir Kandoi
a436c6a503 refactor: creation of purchase order from sales order 2025-11-25 12:16:48 +05:30
Smit Vora
8235a551f0 refactor: further changes to adapt to query builder changes 2025-11-25 12:08:31 +05:30
Rohit Waghchaure
ca47ae6fd8 fix: stock reservation validation 2025-11-25 12:01:51 +05:30
sokumon
3d0b28a198 fix: re-export all sidebars 2025-11-25 11:17:29 +05:30
ruthra kumar
58e217be6a Merge pull request #50561 from aerele/hidden-fields-ledger-summary
fix(ledger-summary-report): show party group and territory
2025-11-25 11:07:23 +05:30
Logesh Periyasamy
5e58e344b2 feat(accounting-dimension): add dynamic triggers for custom accounting dimensions (#50621)
* feat: add dynamic triggers for custom accounting dimensions

* feat: add accounting dimension trigger call in setup event

* chore: ignore cur_frm semgrep rules

* chore: move function to transaction.js
2025-11-25 10:21:17 +05:30
Mihir Kandoi
c2b8b97d7d fix: incorrect query filter when selecting primary customer adr (#50727) 2025-11-25 04:48:04 +00:00
MochaMind
fa8007f949 fix: sync translations from crowdin (#50699) 2025-11-24 19:27:59 +01:00
ljain112
40b787827a fix: use alias for get_exchange_rate function in JournalEntry 2025-11-24 18:04:56 +05:30
ruthra kumar
33135899ab Merge pull request #50706 from frappe/pot_develop_2025-11-23
chore: update POT file
2025-11-24 17:20:57 +05:30
ljain112
50cf814b43 Merge branch 'develop' into tds-jv 2025-11-24 16:47:30 +05:30
Khushi Rawat
5806bcbb17 Merge pull request #50716 from elshafei-developer/add-missing-translate-function
fix: add missing translate function
2025-11-24 13:07:40 +05:30
El-Shafei H.
56def01240 fix: add missing translate function 2025-11-24 09:51:42 +03:00
Kavin
d01c4b68fe fix: add validation for FG Items as per BOM qty (#50579)
Co-authored-by: Kavin <78342682+kavin0411@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-11-24 06:17:14 +00:00
Mihir Kandoi
aeece36d93 Merge pull request #50712 from mihir-kandoi/gh50703 2025-11-24 11:30:50 +05:30
Mihir Kandoi
49c866db74 chore: change manufacturing course link 2025-11-24 11:12:06 +05:30
Mihir Kandoi
e2a01773a5 Merge pull request #50661 from aerele/support-53364 2025-11-24 10:38:17 +05:30
Mihir Kandoi
903e97af5f Merge pull request #50710 from mihir-kandoi/fix-phantom-subassembly 2025-11-23 21:52:09 +05:30
Mihir Kandoi
7b1d860c33 fix: subassembly inside phantom item not being fetched in production plan 2025-11-23 21:33:38 +05:30
Mihir Kandoi
5659538e67 Merge pull request #50707 from mihir-kandoi/pricing-rule-error 2025-11-23 19:37:44 +05:30
Mihir Kandoi
3b7d7aed4c fix: unknown column error 2025-11-23 19:16:39 +05:30
frappe-pr-bot
3404419a1f chore: update POT file 2025-11-23 09:36:01 +00:00
Diptanil Saha
47a6d34224 Merge pull request #50476 from aerele/support-53067 2025-11-21 22:10:37 +05:30
Lakshit Jain
d3c33d16ad Merge pull request #50609 from karm1000/patch/handle-empty-item-tax-rate
fix: handle empty item_tax_rate in ItemTax class
2025-11-21 18:27:02 +05:30
Karm Soni
fc098a732b fix: handle empty item_tax_rate in ItemTax class 2025-11-21 16:47:23 +05:30
Smit Vora
e92c46ba19 Merge pull request #50658 from ljain112/fix-taxes-validation 2025-11-21 16:43:59 +05:30
rohitwaghchaure
5ded5e54f1 Merge pull request #50660 from rohitwaghchaure/fixed-repost-gl-only
feat: repost GL Entries only
2025-11-21 16:06:31 +05:30
Rohit Waghchaure
b01f872f7d feat: repost GL Entries only 2025-11-21 15:21:38 +05:30
Sagar Vora
d5120efa25 Merge pull request #50675 from sagarvora/fix-item-wise-tax-detail-patch 2025-11-21 14:23:09 +05:30
Sagar Vora
f644c19760 fix: ignore chunk if no valid invoices found 2025-11-21 14:04:15 +05:30
Akhil Narang
42f4e7ebde Merge pull request #50659 from akhilnarang/qb-compat
fix: adjust a few more queries
2025-11-21 13:43:14 +05:30
Mihir Kandoi
d5025b2af8 Merge pull request #50667 from mihir-kandoi/gh50122 2025-11-21 12:50:48 +05:30
Mihir Kandoi
5fe3fcf174 Merge pull request #50655 from mihir-kandoi/gh49528 2025-11-21 12:43:27 +05:30
Diptanil Saha
47bb2544b9 Merge pull request #50669 from diptanilsaha/gh-50435 2025-11-21 12:25:32 +05:30
diptanilsaha
cd145f4141 fix(lead): made the create and action menu visible for lead doctype 2025-11-21 12:22:00 +05:30
Pugazhendhi Velu
9194e6350a fix: apply precision for scrap items amount 2025-11-21 06:43:20 +00:00
Mihir Kandoi
ffae7c4175 fix: pricing rule was ignoring time validity 2025-11-21 11:58:33 +05:30
Mihir Kandoi
d26f8aa629 fix: tests 2025-11-21 11:23:06 +05:30
Pugazhendhi Velu
5fd7d46986 Merge branch 'develop' of https://github.com/aerele/erpnext into support-53364 2025-11-21 05:44:52 +00:00
Diptanil Saha
866f1e695b Merge pull request #50665 from diptanilsaha/st-53822 2025-11-21 06:47:39 +05:30
diptanilsaha
310099f4cd fix(customer): link contact and addresses if created from lead/opportunity/prospect 2025-11-21 06:18:43 +05:30
MochaMind
7422464e75 fix: sync translations from crowdin (#50664) 2025-11-21 00:44:07 +01:00
Pugazhendhi Velu
02941afd6a Merge branch 'develop' of https://github.com/frappe/erpnext into support-53364 2025-11-20 13:31:25 +00:00
Akhil Narang
13f8bcd289 fix: adjust a few more queries
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-11-20 18:14:34 +05:30
ljain112
ef37e6aa16 fix: handle zero rate actual taxes in calculate_taxes_and_totals 2025-11-20 18:13:30 +05:30
Mihir Kandoi
f7b3253683 fix: pick list status doesn't update when DN created from it and PL was created from SO 2025-11-20 17:40:28 +05:30
rohitwaghchaure
8f01e89d76 Merge pull request #50639 from mihir-kandoi/st47854
fix(product bundle): fields reset if doc is new
2025-11-20 16:12:00 +05:30
rohitwaghchaure
8a40eac45a Merge pull request #50649 from mihir-kandoi/zero-val-stock-reco
fix: unhide zero val checkbox in stock reco
2025-11-20 16:11:34 +05:30
Mihir Kandoi
7172f30455 Merge pull request #50385 from NihalRoshanCK/disable-warehouse 2025-11-20 16:02:09 +05:30
Mihir Kandoi
ff2d9bf4cb fix: remove disabled warehouse in get_warehouses_based_on_account 2025-11-20 15:42:27 +05:30
Mihir Kandoi
20e0313a8c fix: unhide zero val checkbox 2025-11-20 15:34:39 +05:30
Mihir Kandoi
7faee7edc2 fix(product bundle): fields reset if doc is new 2025-11-20 15:32:55 +05:30
rohitwaghchaure
21361ebb2f Merge pull request #50646 from mihir-kandoi/gh49446
fix: serial batch selector shown only once
2025-11-20 15:07:44 +05:30
Mihir Kandoi
aa6f09e9a9 fix: serial batch selector shown only once 2025-11-20 14:57:33 +05:30
rohitwaghchaure
5e47b0dadb Merge pull request #50644 from rohitwaghchaure/fixed-delete-sabb-validation
fix: validation for SABB deletion
2025-11-20 13:45:36 +05:30
Khushi Rawat
8a57090aa2 Merge pull request #50286 from khushi8112/budget-feature-enhancements
feat: Budget feature enhancements
2025-11-20 13:07:03 +05:30
Rohit Waghchaure
dd4bef0706 fix: validation for SABB deletion 2025-11-20 13:00:24 +05:30
Jatin3128
4b612c64a8 fix(payment reconciliation): added a hint that posting date can be changed on exchange gain/loss reconcile dialog 2025-11-20 12:53:29 +05:30
Mihir Kandoi
c3e735ae96 Merge pull request #50502 from aerele/add-purchase-invoice-link-field 2025-11-20 12:33:11 +05:30
khushi8112
c3ff5e3748 fix: multiple minor fixes 2025-11-20 12:18:03 +05:30
ruthra kumar
ee69a6b8ab Merge pull request #50636 from frappe/l10n_develop
fix: sync translations from crowdin
2025-11-20 10:57:12 +05:30
Mihir Kandoi
4e6d288056 Merge pull request #50635 from aerele/negative-conversion-factor 2025-11-20 10:55:00 +05:30
MochaMind
cb737f31fc fix: Bosnian translations 2025-11-19 14:23:42 -08:00
MochaMind
1d850bf3be fix: Croatian translations 2025-11-19 14:23:39 -08:00
MochaMind
fdb790b00f fix: Persian translations 2025-11-19 14:23:35 -08:00
MochaMind
29f9e423b2 fix: Swedish translations 2025-11-19 14:23:29 -08:00
SowmyaArunachalam
6141071a18 fix(uom): validate negative conversion factor 2025-11-19 22:05:19 +05:30
Akhil Narang
0cb734d6a0 Merge pull request #50550 from akhilnarang/qb-compat
refactor: adapt for query builder changes
2025-11-19 21:55:25 +05:30
Akhil Narang
1cf9f903e5 fix: adapt to query builder
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-11-19 21:36:37 +05:30
Rohit Waghchaure
d40e660a52 fix: use qb for functions 2025-11-19 21:36:37 +05:30
Pugazhendhi Velu
57f9353d90 fix(manufacturing): apply precision for bom amount and rm_cost_per_qty 2025-11-19 14:19:26 +00:00
khushi8112
acec1a7a9d fix: permission based revision of budget 2025-11-19 17:05:26 +05:30
rohitwaghchaure
ec4d4a0d6c Merge pull request #50624 from rohitwaghchaure/fixed-desktop-icons-accounts
fix: desktop icons
2025-11-19 16:38:16 +05:30
Mihir Kandoi
1ee700fff3 fix: process loss % can be negative (#50629) 2025-11-19 11:07:23 +00:00
ruthra kumar
197f00f211 Merge pull request #50623 from vorasmit/fix-custom-fs-js
fix: replace `this` with function path
2025-11-19 16:19:12 +05:30
Mihir Kandoi
5fdf8058df Merge pull request #48634 from mihir-kandoi/46788 2025-11-19 16:17:47 +05:30
Rohit Waghchaure
124293bd63 fix: desktop icons 2025-11-19 16:16:21 +05:30
Mihir Kandoi
38cf0d9b5f Merge pull request #50627 from mihir-kandoi/gh48474 2025-11-19 16:16:19 +05:30
Mihir Kandoi
3271eaaf0e fix: show current company warehouse only in get material from bom MR 2025-11-19 16:13:56 +05:30
Mihir Kandoi
5ba4c1ea22 Merge pull request #50625 from mihir-kandoi/gh48301 2025-11-19 16:11:32 +05:30
Mihir Kandoi
3ca3a6d9bb fix: add filter company and status to job card employee 2025-11-19 16:07:50 +05:30
Mihir Kandoi
3327799524 test: add test case 2025-11-19 15:57:15 +05:30
Mihir Kandoi
0973dbac65 fix: create job card button 2025-11-19 15:56:38 +05:30
khushi8112
8fd5d7187a refactor: replace args with params 2025-11-19 15:56:32 +05:30
khushi8112
9ebf546e1f refactor: patch for migration 2025-11-19 15:56:32 +05:30
khushi8112
4a03462890 refactor: use params instead of args 2025-11-19 15:56:32 +05:30
khushi8112
4576ccbbdc fix: use new naming series 2025-11-19 15:56:32 +05:30
khushi8112
22ec48159e fix(minor): use corrct field name in patch 2025-11-19 15:56:32 +05:30
khushi8112
e08793cb8f fix(patch): update naming series for budget 2025-11-19 15:56:32 +05:30
khushi8112
4abe2e82a0 fix(patch): migrate old Budget data to new structure 2025-11-19 15:56:32 +05:30
khushi8112
57f9faa15a fix: validate existing expenses when revising or modifying budget amounts 2025-11-19 15:56:32 +05:30
khushi8112
09ed3066d8 fix: test cases and fiscal year validation 2025-11-19 15:56:32 +05:30
khushi8112
04a44e7e14 refactor: budget controller 2025-11-19 15:56:32 +05:30
khushi8112
e4bae76580 refactor: add budget start and end date field on the parent 2025-11-19 15:56:32 +05:30
khushi8112
e40fe9919c refactor: better manual budget distribution ux 2025-11-19 15:56:32 +05:30
khushi8112
1cb03db43b test: test cases to validate budget distribution and revision 2025-11-19 15:56:32 +05:30
khushi8112
bd88356a8a feat: budget for multiple fiscal year 2025-11-19 15:56:32 +05:30
khushi8112
b5d892c802 fix: default company currency for amount 2025-11-19 15:56:32 +05:30
khushi8112
1f832ca23e fix: test cases of budget 2025-11-19 15:56:32 +05:30
khushi8112
64456af654 refactor: update budget expense validation to align with new structure 2025-11-19 15:56:32 +05:30
khushi8112
af9dc8e406 test: budget revision test cases 2025-11-19 15:56:32 +05:30
khushi8112
077692b57b feat: Budget Revision 2025-11-19 15:56:32 +05:30
khushi8112
882b6c2950 feat: add budget amount field on parent 2025-11-19 15:56:32 +05:30
khushi8112
d8deb33c8c feat: auto-generate budget distribution rows based on start and end date 2025-11-19 15:56:32 +05:30
khushi8112
8857037971 feat: flexible budget allocation frequency 2025-11-19 15:56:31 +05:30
khushi8112
ccb89fee75 feat: add fields for new budget flow 2025-11-19 15:56:31 +05:30
khushi8112
e23d229e7b feat: introduce budget distribution child table 2025-11-19 15:56:31 +05:30
khushi8112
906a4bd398 feat(patch): set total budget amount on budget doctype 2025-11-19 15:56:31 +05:30
khushi8112
b6e452a695 feat: show budget total 2025-11-19 15:56:31 +05:30
Mihir Kandoi
37b120bf69 fix: modify for new changes 2025-11-19 15:56:13 +05:30
rohitwaghchaure
be40b5bbff Merge pull request #50622 from rohitwaghchaure/fixed-validate-reserved-batch
fix: validate reserved batches
2025-11-19 15:51:30 +05:30
Smit Vora
2a1eb08b08 fix: replace this with function path 2025-11-19 15:50:42 +05:30
Rohit Waghchaure
ae0d9d1134 fix: validate reserved batches 2025-11-19 15:12:12 +05:30
Mihir Kandoi
59c3eef7db fix: stock entry manufacture - fix operating cost calculation 2025-11-19 13:00:47 +05:30
Mihir Kandoi
4efe681a5c Merge pull request #50617 from mihir-kandoi/mergify-16-beta-label 2025-11-19 12:49:08 +05:30
Mihir Kandoi
83dab5db60 feat: backport v16 beta label 2025-11-19 12:46:45 +05:30
Mihir Kandoi
d1595a2549 Merge pull request #50614 from mihir-kandoi/fix-duplicate-message 2025-11-19 12:35:11 +05:30
Mihir Kandoi
074f07694f fix: redundant message on bom save 2025-11-19 12:11:56 +05:30
MochaMind
dea734cd4c fix: sync translations from crowdin (#50613) 2025-11-19 01:11:26 +01:00
MochaMind
cf73de9533 chore: update POT file (#50547) 2025-11-18 15:39:39 +01:00
rohitwaghchaure
f13e540e74 Merge pull request #50512 from aerele/support-53201
fix: add return status for purchase receipt
2025-11-18 18:02:27 +05:30
rohitwaghchaure
1512c94e79 Merge pull request #50486 from aerele/support-52693
fix: validate sabb autocreation when disabled
2025-11-18 17:58:22 +05:30
ruthra kumar
d2c19007cc Merge pull request #50524 from aerele/financial-ratio-calculation-fix
fix: use dynamic account type to get average ratio balance
2025-11-18 16:52:17 +05:30
Khushi Rawat
9b374d605a Merge pull request #50591 from khushi8112/validate-depreciation-account-type
fix: validate account type of depreciation account
2025-11-18 16:48:18 +05:30
Logesh Periyasamy
113ff17c71 fix(general_ledger): add translation for accounting dimension 2025-11-18 16:45:15 +05:30
Logesh Periyasamy
9670edb521 fix: add condition for allow negative stock in pos (#50369) 2025-11-18 16:37:24 +05:30
Raffael Meyer
4d9473f844 feat(Company): allow setting default sales contact, fetch into sales transaction (#50159) 2025-11-18 16:33:21 +05:30
khushi8112
68d6fc142b fix: check root type instead of account type 2025-11-18 16:26:35 +05:30
khushi8112
ca37f0371b fix: validate root type as well 2025-11-18 15:26:02 +05:30
ruthra kumar
73eaddcd67 fix: unintended backported depends_on expression (backport #50529) (#50587)
fix: unintended backported depends_on expression (#50529)


(cherry picked from commit 81a16286a1)

Co-authored-by: Kavin <78342682+kavin-114@users.noreply.github.com>
Co-authored-by: Kavin <78342682+kavin0411@users.noreply.github.com>
2025-11-18 15:05:27 +05:30
khushi8112
592ec1c5a5 fix: validate account type of depreciation account 2025-11-18 14:51:46 +05:30
rohitwaghchaure
b1be525032 Merge pull request #50585 from rohitwaghchaure/fixed-desktop-icons-erpnext
fix: icons for workspace sidebar in ERPNext modules
2025-11-18 14:10:56 +05:30
Rohit Waghchaure
842546d917 fix: icons for workspace sidebar in ERPNext modules 2025-11-18 13:49:33 +05:30
l0gesh29
8f91919933 test: add party_group, territory in json 2025-11-18 13:42:45 +05:30
Kavin
67d471598d fix: unintended backported depends_on expression (#50529)
Co-authored-by: Kavin <78342682+kavin0411@users.noreply.github.com>
(cherry picked from commit 81a16286a1)
2025-11-18 08:08:07 +00:00
Ejaaz Khan
154350b733 Merge pull request #50583 from iamejaaz/fix-item-patch
fix: NoneType issue on item tax rate
2025-11-18 12:00:44 +05:30
Ejaaz Khan
9ea3e1e848 fix: NoneType issue on item tax rate 2025-11-18 11:40:20 +05:30
rohitwaghchaure
b672744543 Merge pull request #50578 from rohitwaghchaure/fixed-hide-home-icon
fix: hide home icon
2025-11-17 21:16:43 +05:30
Rohit Waghchaure
4e1e2ee756 fix: hide home icon 2025-11-17 20:58:23 +05:30
rohitwaghchaure
6277dac209 Merge pull request #50577 from rohitwaghchaure/fixed-desktop-icons
fix: desktop icons
2025-11-17 19:58:55 +05:30
Rohit Waghchaure
9349dcc907 fix: desktop icons 2025-11-17 19:40:51 +05:30
rohitwaghchaure
2ea6921e20 Merge pull request #50575 from sokumon/desktop-icons
fix: add more icons
2025-11-17 19:37:41 +05:30
sokumon
12a2e14dae fix: add more icons 2025-11-17 19:12:31 +05:30
Smit Vora
12008b775f Merge pull request #50439 from ljain112/fix-lead-quotation 2025-11-17 19:08:31 +05:30
Smit Vora
5662801a9c Merge pull request #50496 from ljain112/correct-grand-total 2025-11-17 19:05:45 +05:30
Lakshit Jain
91f3c82bdf feat!: Item Wise Tax Details Table (#48692)
* fix: Add `Item Wise Tax Detail` Table and update related doctypes

* fix: remove setting item_wise_tax_details in client side

* fix: Remove redundant code for updating item_wise_tax_details after rename

* fix: Add 'dont_recompute_tax' field to Item Wise Tax Detail

* fix: update item_wise_tax_details after validations

* chore: remove redundant code from payment_entry.js

* fix: changes in POS for item_wise_tax_details

* fix: handle merge taxes

* fix: update test case and fix precision issue

* chore: remove debugging statement

* chore: remove redundant import

* chore: linters

* chore: remove redundant code and minor refactor

* fix: correct function args

* fix: fix test cases

* fix: item wise sales register report

* fix: remove dont recompute from item wise tax details and calculation for deduct

* fix: do not retain old rows

* fix: added validation for item wise tax details

* fix: tax merging for pos

* fix: vat audit report(regional report)

* fix: query issue in item-wise sales register

* fix: set other_charges using temp object

* fix: precision issue in validation

* fix: changes as per failing test cases

* fix: tax merging

* fix: set no_copy for item wise tax detail

* fix: correct select field in query and other charged in item_wise_purchase_register

* fix: do not include rows with missing item or tax in merge_taxes

* fix: respect row wise rounding

* chore: remove unused import

* chore: incorrect tuple creation

* fix: handle rounding adjustment

* fix: currency option in item wise tax detail doctype

* fix: patch to migrate item_wise tax_details to table

* chore: remove item_wise_tax_detail from taxes table

* fix: use base_tax_withholding_net_total instead of tax_withholding_net_total

* fix: implemet item_wise_tax_detail for e-invoice (italy)

* fix: fetch document by doctypes in migration patch

* fix: fix multiple syntax errors and inconsistent variable usage

* fix: remove deprecated settings and update item wise tax details flag

* fix: enhance validation for item wise tax details and handle discrepancies

* fix: increase chunk size for migration and improve item-wise tax detail calculations

* fix: delete existing item-wise tax details to prevent duplicates during migration

* fix: remove unnecessary docstatus filter from tax details query

* fix: streamline validation checks in item wise tax details adjustment

* fix: update additional fields to reference item and invoice attributes in tax detail queries

* fix: Restrict tax query to the selected invoices in vat audit report

* fix: use `base_tax_withholding_net_total` for calculation in patch

* fix: set tax row_id and idx to None instead of empty strings

* fix: remove unused precision parameter from rounding differences handler

* fix: update docstatus in item_wise_tax_details as per doc

* fix: remove empty on_update method from SalesOrder class

* fix: remove empty on_update method from PurchaseOrder class

* fix: incorporate zero cutoff in tax calculation logic

* fix: increase threshold for rounding diff
2025-11-17 19:02:31 +05:30
rohitwaghchaure
47b7214580 Merge pull request #50567 from sokumon/desktop-icons
feat: add newly created desktop icons
2025-11-17 17:33:32 +05:30
sokumon
768afd7968 feat: add newly created desktop icons 2025-11-17 17:10:00 +05:30
rohitwaghchaure
ec5849d8d2 Merge pull request #50492 from nabinhait/erpnext-workspace-sidebars
refactor: Workspace sidebars and desktop icons for erpnext modules
2025-11-17 16:55:23 +05:30
Rohit Waghchaure
fdbe7bc988 fix: desktop icon placing 2025-11-17 16:35:03 +05:30
Rohit Waghchaure
8ea5170fb8 fix: sidebars of erpnext modules 2025-11-17 15:25:50 +05:30
Mihir Kandoi
350539f5e6 Merge pull request #50562 from mihir-kandoi/gh50536 2025-11-17 14:53:29 +05:30
Mihir Kandoi
4cde0bfddd fix: incorrect connection to delivery note on pick list 2025-11-17 14:34:16 +05:30
ruthra kumar
05ee4c0b0c Merge pull request #50560 from frappe/mergify/copy/develop/pr-49788
feat: (Multi-Currency in Employee Advance, Expense Claim)  update exchange rate of payment entry in gl entry & added exchange rate, base amount field in Advance Payment Ledger Entry (copy #49788)
2025-11-17 14:07:35 +05:30
Rohit Waghchaure
56e44cfccb fix: sidebar for selling and accounting modules 2025-11-17 13:55:01 +05:30
l0gesh29
231479a6e2 fix(ledger-summary-report): show party group and territory 2025-11-17 13:42:43 +05:30
iamkhanraheel
054e7adeac fix: conflicts 2025-11-17 13:37:25 +05:30
iamkhanraheel
9c2525a8f4 feat: add exchange rate & base field in advance payment ledger, set exchange rate in journal entry on every refresh
(cherry picked from commit 287cb621cd)

# Conflicts:
#	erpnext/accounts/doctype/advance_payment_ledger_entry/advance_payment_ledger_entry.json
2025-11-17 08:04:55 +00:00
iamkhanraheel
bacef2f135 fix: remove hrms dependent func which are moved to hrms module
(cherry picked from commit 038536e1cd)
2025-11-17 08:04:54 +00:00
iamkhanraheel
78da4c38fa feat: multiCurrency in epxense claim, set excahnge rate in advance & update it in gl entry
(cherry picked from commit a9ecf7c319)
2025-11-17 08:04:54 +00:00
Rohit Waghchaure
334deccd2d feat: workspace sidebars and desktop icons for erpnext modules 2025-11-17 13:20:57 +05:30
rohitwaghchaure
b5df39f47d Merge pull request #50388 from rohitwaghchaure/feat-subcontracting-workspace
feat: subcontracting workspace and sidebar
2025-11-17 13:08:50 +05:30
Asmita Hase
8ca02e7fbb Merge pull request #50538 from asmitahase/holiday-list-assignment 2025-11-17 12:11:49 +05:30
Asmita Hase
7362d783b1 Merge branch 'develop' into holiday-list-assignment 2025-11-17 11:53:56 +05:30
rohitwaghchaure
60f8654ad6 Merge pull request #50506 from mihir-kandoi/company-wise-valuation-method
feat: company wise valuation method
2025-11-17 11:49:09 +05:30
ruthra kumar
b2a0cdf4bc Merge pull request #50552 from frappe/l10n_develop
fix: sync translations from crowdin
2025-11-17 11:27:49 +05:30
Khushi Rawat
c4b9268f9d Merge pull request #50404 from khushi8112/company-details-popup-trigger
fix: show company-details popup only for the targeted print format/le…
2025-11-17 11:15:59 +05:30
Mihir Kandoi
316b6d6867 feat: company wise valuation method 2025-11-17 10:37:11 +05:30
Mihir Kandoi
5dcb766b9f Merge pull request #50540 from aerele/support-52430 2025-11-17 10:10:48 +05:30
MochaMind
03bfbeb1cb fix: Persian translations 2025-11-16 14:14:08 -08:00
MochaMind
14c5245037 fix: sync translations from crowdin (#50503)
* fix: Persian translations

* fix: Bosnian translations

* fix: Hungarian translations

* fix: French translations

* fix: Arabic translations

* fix: Polish translations

* fix: Turkish translations

* fix: Indonesian translations

* fix: Persian translations

* fix: Spanish translations
2025-11-16 15:08:37 +01:00
Mihir Kandoi
42002d0aa1 Merge pull request #50507 from mihir-kandoi/company-wise-default-warehouses 2025-11-16 17:10:11 +05:30
Mihir Kandoi
84af60da7f feat: company wise default warehouses 2025-11-16 16:51:51 +05:30
rohitwaghchaure
c4882f6f26 fix: validation to check company in Sales Forecast and MPS (#50545) 2025-11-16 15:08:31 +05:30
Mihir Kandoi
e5e26cd92a feat: phantom bom (#50351)
* feat: add phantom bom settings in bom doctype

* feat: new explosion logic for production plan, subcontracting and work order/manufacturing

* feat: modify explosion logic in reports and bom creator

* fix: failing test

* feat: add convert to phantom item support in bom creator

* test: added test cases

* fix: always fetch rm rate if phantom bom

* refactor: PP phantom explosion logic

* fix: report test cases

* feat: add phantom item in description of item if phantom item in bom tree

* fix: hide create button if bom is phantom

* fix: bugs found by coderabbit
2025-11-16 15:08:08 +05:30
Mihir Kandoi
9b303a2272 Merge pull request #50235 from mihir-kandoi/sre-sco
feat: stock reservation for subcontracting order
2025-11-16 13:06:21 +05:30
Rohit Waghchaure
80b6c226b2 fix: validation to check company in Sales Forecast and MPS 2025-11-16 12:58:29 +05:30
Pugazhendhi Velu
8b38578914 fix(stock-entry): prevent default warehouse from overriding parent warehouse 2025-11-16 06:07:55 +00:00
Raffael Meyer
4bd3b00e5f refactor: remove unused import (#50543) 2025-11-15 19:34:44 +00:00
Raffael Meyer
38287afc05 fix: mark role profile names as translatable (#50542) 2025-11-15 19:26:12 +00:00
Raffael Meyer
f57d7f39bc fix: mark navbar item as translatable (#50541) 2025-11-15 19:11:39 +00:00
Raffael Meyer
9a989a84fb fix: use dummy translations for custom field labels (#49875) 2025-11-15 19:34:13 +01:00
Asmita Hase
3ce6da3b71 feat: half day in holiday list 2025-11-15 18:35:14 +05:30
Asmita Hase
0af4515afd feat: specify half day in holiday list 2025-11-15 18:35:12 +05:30
Smit Vora
68cdadf11a feat: support custom financial statements (#49098)
Co-authored-by: Abdeali Chharchhoda <abdealiking786@gmail.com>
2025-11-15 09:59:01 +05:30
Navin-S-R
f420371a7e fix: correct profit after tax calculation by reducing expenses from income 2025-11-14 14:12:29 +05:30
Navin-S-R
9118f08e7b fix: use dynamic account type to get average ratio balance 2025-11-14 14:05:47 +05:30
Mihir Kandoi
b98f4611e6 fix: wrong currency in Subcontracting Order Service Item (#50517) 2025-11-14 05:21:06 +00:00
PUGAZHENDHI V
4f720b3969 fix(period closing voucher): add title to error log (#50498) 2025-11-13 21:25:44 +05:30
Pugazhendhi Velu
3a0e1e8ef9 fix: add return status for purchase receipt 2025-11-13 15:29:27 +00:00
rohitwaghchaure
75533ea7d8 Merge pull request #50515 from rohitwaghchaure/fixed-sales-forecast-manual
fix: do not allow to add rows manually
2025-11-13 18:16:34 +05:30
Rohit Waghchaure
019f8103f3 fix: do not allow to add rows manually 2025-11-13 16:34:31 +05:30
rohitwaghchaure
b79f88a0a6 Merge pull request #50513 from rohitwaghchaure/fixed-seasonal-method
fix: change seasonal method
2025-11-13 15:47:41 +05:30
Diptanil Saha
ef7a3419fa Merge pull request #50510 from ljain112/fix-supplier-quick-entry 2025-11-13 15:26:48 +05:30
Rohit Waghchaure
58c92ea10d fix: change seasonal method 2025-11-13 15:11:31 +05:30
ljain112
510f50077b fix: first and last name in supplier quick entry 2025-11-13 14:24:35 +05:30
Mihir Kandoi
eafd2d4b5f Merge pull request #50509 from mihir-kandoi/gh49650 2025-11-13 14:07:04 +05:30
Mihir Kandoi
2ea9ffea3c fix(stock): format numeric values in variant dialog helper text 2025-11-13 14:03:38 +05:30
Diptanil Saha
37aa24141b Merge pull request #50495 from ljain112/chore-description-not-mandatory 2025-11-13 12:21:28 +05:30
rohitwaghchaure
926c670c91 Merge pull request #50501 from rohitwaghchaure/fixed-schedule-validation
fix: validation for delivery schedule
2025-11-13 10:49:39 +05:30
Karuppasamy B
6c1620ab8c fix(purchase_receipt): add internal_and_external_links field to show purchase invoice connection count 2025-11-13 01:16:48 +05:30
Rohit Waghchaure
31ae91f313 fix: validation for delivery schedule 2025-11-12 23:53:11 +05:30
ljain112
e056c0327d chore: typo in comment 2025-11-12 19:28:38 +05:30
ljain112
7c5f5405cc fix: improve precision in tax amount calculations in tax withholding details report 2025-11-12 18:59:45 +05:30
ljain112
d3751d9bb4 fix: back calcalute total amount from rate and tax_amount in tax withholding details report 2025-11-12 18:41:20 +05:30
ljain112
5cfa71fa47 fix: update description field to be optional in POS and Landed Cost Item 2025-11-12 18:15:32 +05:30
rohitwaghchaure
4ba4aef151 Merge pull request #50487 from rohitwaghchaure/fixed-current-qty-in-stock-reco
fix: current qty in stock reconciliation
2025-11-12 13:54:47 +05:30
Rohit Waghchaure
58315bc963 fix: current qty in stock reco 2025-11-12 12:20:41 +05:30
Kavin
3ca1940881 fix: validate sabb autocreation when disabled 2025-11-12 11:32:41 +05:30
Diptanil Saha
b9affe0cd8 Merge pull request #50409 from diptanilsaha/gh_34023 2025-11-12 11:13:25 +05:30
Diptanil Saha
51d583b6a7 Merge pull request #50323 from Abdeali099/set-company-bank-account 2025-11-12 11:07:29 +05:30
rohitwaghchaure
42751fec6d Merge pull request #50217 from aerele/sales-item-uom
fix(sales): update uom based on the selected item
2025-11-12 11:06:47 +05:30
ruthra kumar
385229b81a Merge pull request #50482 from frappe/l10n_develop
fix: sync translations from crowdin
2025-11-12 09:18:31 +05:30
MochaMind
1fe7ef5f1a fix: Bosnian translations 2025-11-11 13:15:14 -08:00
MochaMind
a211db592d fix: Croatian translations 2025-11-11 13:15:10 -08:00
MochaMind
80d13d6629 fix: Swedish translations 2025-11-11 13:15:04 -08:00
Pugazhendhi Velu
e64b6db2eb test: add minimal test case 2025-11-11 13:18:05 +00:00
Pugazhendhi Velu
7fddbb6dc4 Merge branch 'develop' of https://github.com/frappe/erpnext into validate-company-linked-address-field 2025-11-11 11:44:41 +00:00
Pugazhendhi Velu
5a3fcbedb5 fix: use current_tax_amount value for base_total_taxes_and_charges 2025-11-11 10:26:31 +00:00
Diptanil Saha
be826dba3b Merge pull request #50469 from ljain112/auto-taxes 2025-11-11 15:02:43 +05:30
Sagar Vora
7fb4d67662 Merge pull request #50155 from sagarvora/discount-mapping 2025-11-11 15:01:58 +05:30
rohitwaghchaure
6f20ceba81 Merge pull request #50187 from aerele/support-50973
fix: Update pick list locations quantity
2025-11-11 14:53:15 +05:30
ljain112
3d0a668c50 test: add automatic tax addition for buying controller 2025-11-11 14:00:55 +05:30
Kavin
827f9cc6ef fix: pass UOM null to update when item changes 2025-11-11 13:52:23 +05:30
ljain112
d171dc7328 fix: automatically append taxes if taxes_and_charges is set in Buying controller 2025-11-11 13:41:13 +05:30
ruthra kumar
6474435ede Merge pull request #50366 from aerele/enque-err-per-company
refactor: enqueue exchange rate revaluation per company
2025-11-11 13:04:01 +05:30
Mihir Kandoi
da04225c7d Merge pull request #50461 from mihir-kandoi/mat-transfer-wh-validation 2025-11-11 11:57:39 +05:30
Mihir Kandoi
c7b8461d43 feat: make material transfer warehouse validation optional 2025-11-11 11:39:04 +05:30
Kavin
3f7a60d56c test: add test for pending qty calculation in Pick List 2025-11-11 11:02:34 +05:30
Kavin
6db605c443 fix: Pass stock_qty and picked_qty in transfer entry 2025-11-11 11:00:39 +05:30
Kavin
bd9e240ca5 fix: Update pick list locations quantity 2025-11-11 11:00:38 +05:30
Mihir Kandoi
2b051ebb87 Merge pull request #50446 from aerele/company-warehouse-filter-sales-return 2025-11-11 10:47:24 +05:30
Mihir Kandoi
d0ebe5d675 Merge pull request #50418 from aerele/non_billed_report-filter-stock-items 2025-11-11 10:41:57 +05:30
mahsem
e148a38353 fix: state_to_state_province for translation (#50244)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-11-11 05:07:34 +00:00
Mihir Kandoi
f7585f40ac Merge pull request #50320 from aerele/task-is-group-filter 2025-11-11 10:27:37 +05:30
Mihir Kandoi
584d81cfbb chore: fix typo "show_disables_items" to "show_disabled_items" (#50322) 2025-11-11 10:26:09 +05:30
maasanto
722581cf05 Merge pull request #48912 from maasanto/dont-set-description 2025-11-11 10:15:15 +05:30
Mihir Kandoi
f7c9dc20a8 Merge pull request #50452 from frappe/mergify/bp/develop/pr-49831 2025-11-11 10:11:35 +05:30
Mihir Kandoi
834221b297 Merge pull request #50443 from aerele/support-51054 2025-11-11 10:10:25 +05:30
Mihir Kandoi
1d0cd68d2c Merge pull request #50399 from aerele/support-52332 2025-11-11 10:09:57 +05:30
Rehan Ansari
7baf6ec3d6 fix: add missing stock entry UOM filtering based on item master (#50135)
Co-authored-by: rehansari26 <rehan.ansari@cloverinfotech.com>
2025-11-11 09:58:33 +05:30
Assem Bahnasy
2374cf8bfd Fix: Product Bundle Purchase Order Creation Logic (#49831)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
(cherry picked from commit 5b643433e5)
2025-11-11 04:23:59 +00:00
MochaMind
607cf47312 fix: sync translations from crowdin (#50425) 2025-11-10 21:54:29 +00:00
Diptanil Saha
b03cbdb83a Merge pull request #50339 from aerele/acc-dim-difference-entry 2025-11-10 23:15:55 +05:30
Pugazhendhi Velu
0b614007bb fix: add company filter for default warehouse for sales return 2025-11-10 15:19:55 +00:00
rohitwaghchaure
4bd476293b Merge pull request #50436 from rohitwaghchaure/fixed-serial-no-creation
perf: serial no creation
2025-11-10 20:40:21 +05:30
Diptanil Saha
cad5cbb5ed Merge pull request #50361 from aerele/fix/support-52293 2025-11-10 20:37:19 +05:30
Pugazhendhi Velu
462deb3755 fix: change fieldtype from link to data for document_type in production plan summary 2025-11-10 13:59:27 +00:00
MochaMind
bc351feb3e chore: update POT file (#50440) 2025-11-10 12:28:20 +00:00
Raffael Meyer
87b4f872f8 fix: ignore translations from frappe (#50438) 2025-11-10 13:09:14 +01:00
ljain112
0b91338771 fix: add doctype parameter to lead details for correct company details 2025-11-10 17:34:19 +05:30
Rohit Waghchaure
19a9497273 perf: serial no creation 2025-11-10 16:35:54 +05:30
rohitwaghchaure
80219724f0 Merge pull request #50432 from rohitwaghchaure/fixed-patch-for-sabe
fix: patch for existing data
2025-11-10 16:07:05 +05:30
rohitwaghchaure
46ed52a329 Merge pull request #50416 from rohitwaghchaure/fixed-github-46684
fix: work order status
2025-11-10 15:03:22 +05:30
Rohit Waghchaure
91fcac5785 fix: patch for existing data 2025-11-10 15:02:10 +05:30
rohitwaghchaure
a66d643638 Merge pull request #50424 from rohitwaghchaure/perf-serial-batch-dn
perf: DN submission with SABB
2025-11-10 14:43:08 +05:30
Rohit Waghchaure
f2ad27eb06 perf: DN submission with SABB 2025-11-10 14:01:02 +05:30
ruthra kumar
88823e51c7 Merge pull request #50423 from barredterra/fetch-cc-from-project
fix(buying): fetch Cost Center from Project
2025-11-10 12:32:15 +05:30
rohitwaghchaure
ab9241bc11 Merge pull request #50374 from aerele/support-52577
fix: add validation to reject empty readings
2025-11-10 11:07:36 +05:30
barredterra
bdabcb081a fix(buying): fetch Cost Center from Project 2025-11-09 17:56:24 +01:00
MochaMind
dbfb77f768 chore: update POT file (#50421) 2025-11-09 13:17:36 +01:00
Rohit Waghchaure
ba29f5b858 fix: work order status 2025-11-08 22:55:23 +05:30
diptanilsaha
e35e8968f0 fix: prevent pos opening entry creation for disabled pos profile 2025-11-08 15:29:26 +05:30
diptanilsaha
69016a284f test: added test to validate disabled pos profile 2025-11-08 15:29:18 +05:30
ravibharathi656
1b2e5c9706 fix: show only stock items in delivered items to be billed and received items to be billed reports 2025-11-08 15:09:43 +05:30
rohitwaghchaure
a8f3864905 Merge pull request #50406 from rohitwaghchaure/fixed-github-48153
fix: set operating cost based on bom qty causing incorrect operating …
2025-11-07 23:13:43 +05:30
Rohit Waghchaure
4c5ddf03e2 fix: set operating cost based on bom qty causing incorrect operating costing 2025-11-07 22:48:48 +05:30
rohitwaghchaure
2c4654ab30 Merge pull request #50411 from rohitwaghchaure/fixed-github-47250
fix: removed the validation
2025-11-07 18:01:54 +05:30
diptanilsaha
c5219278fb feat(pos): prevent disabling POS Profile when open POS sessions exist 2025-11-07 17:25:54 +05:30
Rohit Waghchaure
10131333b2 fix: removed the validation 2025-11-07 16:39:13 +05:30
rohitwaghchaure
07f3f420af Merge pull request #50407 from rohitwaghchaure/feat-allow_editing_of_items_and_quantities_in_work_order
feat: Allow Editing of Items and Quantities in Work Order
2025-11-07 16:24:41 +05:30
Rohit Waghchaure
b5e6c3e703 feat: Allow Editing of Items and Quantities in Work Order 2025-11-07 15:46:48 +05:30
Patrick Eißler
4846dfd3ca fix(Timesheet): don't use billing_hours for costing amount calculation (#50392) 2025-11-07 13:26:16 +05:30
Diptanil Saha
6d4afc85e6 Merge pull request #50326 from aerele/support-52064 2025-11-07 12:57:29 +05:30
khushi8112
4c8226eb18 fix: show company-details popup only for the targeted print format/letterhead 2025-11-07 12:55:14 +05:30
Diptanil Saha
9eabaf02c6 Merge pull request #50402 from diptanilsaha/gh_33287 2025-11-07 12:32:44 +05:30
diptanilsaha
f7d09f8760 fix: trends report total mismatch with group filters 2025-11-07 12:08:22 +05:30
Pugazhendhi Velu
55f531bad6 test: add test for validate mr item qty against so with over-receipt allowance 2025-11-06 20:39:42 +00:00
Pugazhendhi Velu
8d7e31e3f2 fix: material request item quantity validation against sales order with over-receipt allowance 2025-11-06 20:39:36 +00:00
Pugazhendhi Velu
4cf02b4d78 refactor(task): use get_link_to_form for validation error messages 2025-11-06 11:22:58 +00:00
Diptanil Saha
a5830c8247 Merge pull request #50340 from aerele/transaction_deletion_record 2025-11-06 16:44:33 +05:30
Pugazhendhi Velu
ed40b3232e Merge branch 'develop' of https://github.com/frappe/erpnext into support-52064 2025-11-06 11:06:45 +00:00
Rohit Waghchaure
8ef18754a0 feat: subcontracting workspace and sidebar 2025-11-06 15:48:10 +05:30
Diptanil Saha
70483cffa3 Merge pull request #50364 from aerele/support-52595 2025-11-06 15:39:42 +05:30
Pugazhendhi Velu
63fb9f55e7 refactor: add default reading value when creating a quality inspection 2025-11-06 06:32:04 +00:00
Pugazhendhi Velu
67e7a09e08 Merge branch 'develop' of https://github.com/frappe/erpnext into support-52577 2025-11-06 06:17:24 +00:00
Khushi Rawat
5cc2cf530a Merge pull request #50367 from rehanrehman389/asset-mov-fix
fix: set company before creating asset movement to avoid permission error
2025-11-06 03:19:29 +05:30
Khushi Rawat
a0b9b5cd71 Merge pull request #50342 from rehanrehman389/asset-name
feat: add asset name column
2025-11-06 00:21:42 +05:30
Khushi Rawat
3820be8a64 Merge pull request #50343 from rehanrehman389/asset-fix
fix: validate purchase invoice status and resolve related issues
2025-11-06 00:18:59 +05:30
Pugazhendhi Velu
405d901514 fix: add validation to reject empty readings 2025-11-05 17:34:55 +00:00
Pugazhendhi Velu
e10007c646 test: add test for company linked address fields 2025-11-05 15:02:57 +00:00
Pugazhendhi Velu
800a44a65f fix: add validation for company linked address fields 2025-11-05 15:02:02 +00:00
MochaMind
cc7810998c fix: sync translations from crowdin (#50281) 2025-11-05 15:25:10 +01:00
ruthra kumar
ad42eae2d6 Merge pull request #50144 from ruthra-kumar/stable_period_closing_voucher
refactor: period closing voucher to handle large data volumes
2025-11-05 16:53:31 +05:30
rehansari26
8c49c9e500 fix: set company before creating asset movement to avoid permission error 2025-11-05 16:46:00 +05:30
ravibharathi656
b10e7bf7b5 refactor: enqueue exchange rate revaluation per company 2025-11-05 16:40:14 +05:30
rohitwaghchaure
e213c64f9e Merge pull request #50363 from rohitwaghchaure/fixed-prevent-sn-reuse
fix: prevent reuse of serial no in manufacture and repack entry
2025-11-05 16:05:05 +05:30
Pugazhendhi Velu
ef38b26a73 fix: hide total row in general ledger report 2025-11-05 10:26:18 +00:00
ruthra kumar
fca7abf4d6 refactor: add paused to select option 2025-11-05 15:51:25 +05:30
ruthra kumar
9c13edc0b9 refactor: minor changes on status
1. set to 'In Progress' on start of both legacy and new controller
2. force delete to avoid permission issues
3. default to 1hr timeout
2025-11-05 15:51:25 +05:30
ruthra kumar
fe39ce03bb refactor: enable legacy controller by default for pcv 2025-11-05 15:51:25 +05:30
ruthra kumar
0b88f98a86 chore: progress bar 2025-11-05 15:51:25 +05:30
ruthra kumar
191c0e65a1 chore: remove scaffolding 2025-11-05 15:51:25 +05:30
ruthra kumar
090e155fd0 refactor: abort processing of all tasks upon cancellation 2025-11-05 15:51:25 +05:30
ruthra kumar
cae1237859 refactor: more changes
1. 'Accounts Manager' has access to submit, cancel and delete
2. cancel and delete operation of PCV is linked with Proces PCV
2025-11-05 15:51:25 +05:30
ruthra kumar
fa3bd6f5a7 refactor: smaller methods 2025-11-05 15:51:25 +05:30
ruthra kumar
7406d83260 refactor: utility to consolidate results from all dates 2025-11-05 15:51:25 +05:30
ruthra kumar
5b464ae4c1 refactor: utility to convert tuple key to str 2025-11-05 15:51:25 +05:30
ruthra kumar
653ae84b3e refactor: cleanup and for better readability 2025-11-05 15:51:25 +05:30
ruthra kumar
6e32769e37 refactor: make Accounts Closing Balance as well 2025-11-05 15:51:25 +05:30
ruthra kumar
09e37bc98c refactor: store closing balance for Balnace sheet accounts 2025-11-05 15:51:25 +05:30
ruthra kumar
643e1fdce8 refactor: calculate both balances from single queue 2025-11-05 15:51:25 +05:30
ruthra kumar
86edacb781 refactor: populate opening balances calculation table 2025-11-05 15:51:25 +05:30
ruthra kumar
cef879bb3b chore: rename closing balance field 2025-11-05 15:51:25 +05:30
ruthra kumar
324bebfd44 refactor: balances for both P&L and Balance sheet accounts 2025-11-05 15:51:25 +05:30
ruthra kumar
186d540502 refactor: maintain report type on each date 2025-11-05 15:51:25 +05:30
ruthra kumar
9e93298f12 refactor: more stable pause and resume 2025-11-05 15:51:25 +05:30
ruthra kumar
c738b6d356 refactor: process on submit 2025-11-05 15:51:25 +05:30
ruthra kumar
8ba199016a refactor: for better readability 2025-11-05 15:51:25 +05:30
ruthra kumar
f25ee3c53f refactor: store results as is and convert at the end 2025-11-05 15:51:25 +05:30
ruthra kumar
e88074ddec refactor: build and post gl entries 2025-11-05 15:51:25 +05:30
ruthra kumar
1846de0d49 refactor: store daily balances based on dimensions key
dimensions key is manually converted to string
2025-11-05 15:51:25 +05:30
ruthra kumar
1a31825409 refactor: store closing balance as JSON 2025-11-05 15:51:25 +05:30
ruthra kumar
c839ebf593 refactor: stable start, pause, resume and completion stages 2025-11-05 15:51:25 +05:30
ruthra kumar
1c92b01542 refactor: barebones functions 2025-11-05 15:51:25 +05:30
ruthra kumar
f44c908a8d refactor: temporarily save balances in JSON 2025-11-05 15:51:25 +05:30
ruthra kumar
0d09d21d2e refactor: child table in process pcv 2025-11-05 15:51:25 +05:30
ruthra kumar
a15578f8f4 refactor: more data structure changes 2025-11-05 15:51:25 +05:30
ruthra kumar
4888461be2 refactor: checkbox for pcv controller 2025-11-05 15:51:25 +05:30
ruthra kumar
7a93630629 feat: process period closing voucher 2025-11-05 15:51:25 +05:30
Rohit Waghchaure
48b537dc8c fix: prevent reuse of serial no in manufacture and repack entry 2025-11-05 15:09:50 +05:30
rohitwaghchaure
fb23719b62 Merge pull request #50335 from aerele/material-request-default-buying-price-list
fix(material request): set default buying price list if not exists
2025-11-05 14:10:59 +05:30
Pugazhendhi Velu
0510f7e13f fix: reset billing and shipping address when company changes 2025-11-05 08:26:34 +00:00
rohitwaghchaure
4d26a796ef Merge pull request #50325 from rohitwaghchaure/fixed-support-51819
fix: negative stock validation
2025-11-05 12:59:07 +05:30
Rohit Waghchaure
eca71dce54 fix: negative stock validation 2025-11-05 11:48:02 +05:30
Rehan Ansari
1928a394c9 fix: validate purchase invoice status and resolve related issues 2025-11-04 23:34:27 +05:30
Rehan Ansari
f3eda02972 feat: add asset name column 2025-11-04 22:08:03 +05:30
rethik
fff6f1fb23 fix: ignore Department doctype 2025-11-04 19:53:44 +05:30
l0gesh29
dcdc1c6a89 fix: apply company,is_group filter for cost center 2025-11-04 18:31:47 +05:30
l0gesh29
4680295303 fix: include cost_center and project upon accounting dimension fetch 2025-11-04 18:30:53 +05:30
Pugazhendhi Velu
60537eeb48 Merge branch 'develop' of https://github.com/frappe/erpnext into support-52064 2025-11-04 11:41:03 +00:00
rohitwaghchaure
7285eaf633 Merge pull request #50332 from rohitwaghchaure/fixed-support-51208
feat: option to exclude stand-alone returned sales invoices from the Gross Profit report
2025-11-04 16:59:39 +05:30
Abdeali Chharchhoda
4901dc2531 fix: on changes of paid from/to account fetch company bank account 2025-11-04 16:28:25 +05:30
ravibharathi656
9c0ff14060 fix(material request): set default buying price list if not exists 2025-11-04 15:52:55 +05:30
Rohit Waghchaure
52cf9d4950 feat: option to exclude stand-alone returned sales invoices from the Gross Profit report 2025-11-04 15:43:15 +05:30
Diptanil Saha
eaf37c606e Merge pull request #50289 from aerele/acc-dim-report
preserve accounting dimension filters while navigating between reports
2025-11-04 15:20:47 +05:30
Mihir Kandoi
dd68578252 chore: add docstring to function (#50329) 2025-11-04 14:17:22 +05:30
Saad Chaudhary
8854db51dd Merge pull request #50299 from saadchaudharry/feature-subcontract-return-rejected
Add support for subcontract return from rejected warehouse
2025-11-04 14:14:34 +05:30
Pugazhendhi Velu
291f0c7161 test: add test for parent task is_group validation 2025-11-04 08:31:41 +00:00
Pugazhendhi Velu
ed1a1099cb fix: validate is_group for parent task 2025-11-04 08:30:54 +00:00
Mihir Kandoi
34c190a76e chore: add docstring to function 2025-11-04 13:57:12 +05:30
Mihir Kandoi
7b8bb4f959 fix: disallow material transfer if source and target warehouse are same (#48697)
* fix: disallow material transfer if source and target warehouse are same

* fix: check on child row level instead of parent level
2025-11-04 13:50:52 +05:30
rethik
d26c598daa chore: fix typo "show_disables_items" to "show_disabled_items" 2025-11-04 11:10:27 +05:30
Pugazhendhi Velu
5bac896329 fix: add is_group filter in task for timesheet 2025-11-03 15:54:00 +00:00
Mihir Kandoi
689eee767d Merge pull request #50319 from mihir-kandoi/misc-sci-fixes
fix: minor issues in subcontracting inward
2025-11-03 18:14:47 +05:30
Mihir Kandoi
553ec40d4a fix: minor issues in subcontracting inward 2025-11-03 17:56:15 +05:30
rohitwaghchaure
34e13ee745 Merge pull request #50318 from rohitwaghchaure/fixed-mrp-issues
fix: multiple MRP issues
2025-11-03 17:35:17 +05:30
Rohit Waghchaure
f43444dd28 fix: multiple MRP issues 2025-11-03 17:13:40 +05:30
rohitwaghchaure
807d344ee1 Merge pull request #50314 from rohitwaghchaure/fixed-stock-reservation-transfer-cancel
fix: stock reservation cancellation for transfer case
2025-11-03 16:54:15 +05:30
Mihir Kandoi
4e9732ab96 Merge pull request #50316 from mihir-kandoi/fix-buying-transporter-filter
revert: remove transporter filter from buying doctypes
2025-11-03 16:31:56 +05:30
Mihir Kandoi
b0cd4bc9e7 revert: remove transporter filter from buying doctypes 2025-11-03 16:28:18 +05:30
Rohit Waghchaure
10094829e8 fix: stock reservation cancellation for transfer case 2025-11-03 15:19:24 +05:30
Kerolles Fathy
44363c069e fix: Respect allowed zero qty in SO and PO based on Buying/Selling settings when update items (#49673)
* fix: respect allowed zero qty in SO and PO based on buying/selling settings

* refactor: remove parent_doctype param due to it's already avaliable

* refactor: rename is_allowed_zero_qty_for to is_allowed_zero_qty

* fix: prevent rate change for unit price items

* fix: unboundlocal variable rate_unchanged

* refactor: only throw on 'zero' qty items

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-11-03 15:16:50 +05:30
Khushi Rawat
5b11710a28 Merge pull request #50260 from khushi8112/gl-entry-only-for-submitted-assets
fix: create GL entries via hooks only for submitted assets
2025-11-03 14:50:38 +05:30
ruthra kumar
d464c731bb Merge pull request #50035 from aerele/fix/accounts-receivable-in-party-currency
fix(accounts-receivable): ensure report data with party account currency
2025-11-03 14:41:07 +05:30
Nihal Roshan
f1682ea90e refactor: remove company filter
'Bank' doctype has no 'company' field.
2025-11-03 14:00:36 +05:30
Diptanil Saha
3e80e99fa6 Merge pull request #50305 from diptanilsaha/st_50933
fix(accounts): populate correct fields on GL Entry during discount accounting
2025-11-03 13:18:52 +05:30
diptanilsaha
e41a7b8cd7 fix(accounts): populate correct fields on GL Entry during discount accounting 2025-11-03 12:58:16 +05:30
ruthra kumar
edf8f83a2a Merge pull request #50258 from barredterra/budget-msg
refactor: error message in budget
2025-11-03 10:15:05 +05:30
Abdeali Chharchhoda
b8e06b9636 refactor: add validation for missing employee parameter 2025-11-02 19:32:34 +05:30
MochaMind
fdd0e39c91 chore: update POT file (#50304) 2025-11-02 09:55:28 +00:00
rohitwaghchaure
4d4086854a Merge pull request #50300 from aerele/support-52253
fix: handle None in last_valuation_rate check
2025-11-02 12:54:10 +05:30
Abdeali Chharchhoda
2ea6508fa5 refactor: fetch employee contact details in realtime 2025-11-01 20:18:52 +05:30
Abdeali Chharchhoda
a41297d841 feat: retrieve employee contact details 2025-11-01 19:18:23 +05:30
Kavin
a3058bf8cc fix: handle None in last_valuation_rate check 2025-11-01 18:16:20 +05:30
Abdeali Chharchhoda
4ad1474e32 feat: retrieve employee basic contact information 2025-11-01 17:55:09 +05:30
Abdeali Chharchhoda
87c59f471c chore: Removing unused import 2025-11-01 17:32:52 +05:30
rohitwaghchaure
ff8b701f13 Merge pull request #50298 from rohitwaghchaure/fixed-github-40130
fix: job card timer for mobile view
2025-10-31 20:18:10 +05:30
Rohit Waghchaure
921bb02eb5 fix: job card timer for mobile view 2025-10-31 20:11:11 +05:30
l0gesh29
fcfcaa76c6 feat(reports): preserve accounting dimension filters while navigating between reports 2025-10-31 19:16:35 +05:30
rohitwaghchaure
2b6723c3d8 Merge pull request #50296 from rohitwaghchaure/fixed-github-48129
fix: hourly rate not fetched on selection of workstation type
2025-10-31 17:02:55 +05:30
Rohit Waghchaure
0aec896ebb fix: hourly rate not fetched on selction of workstation type 2025-10-31 16:59:21 +05:30
rohitwaghchaure
acafc9c2de Merge pull request #50294 from rohitwaghchaure/fixedd-github-48221
fix: pause button not working for sub-operation
2025-10-31 16:57:18 +05:30
rohitwaghchaure
50cceaca6a Merge pull request #50293 from rohitwaghchaure/fixed-github-49402
fix: stock reservation validation for stock reconciliation
2025-10-31 16:37:32 +05:30
Rohit Waghchaure
1f183a7e06 fix: pause button not working for sub-operation 2025-10-31 16:36:14 +05:30
Rohit Waghchaure
859dc3b458 fix: stock reservation validation for stock reconciliation 2025-10-31 16:07:43 +05:30
Mihir Kandoi
65de8b4915 Merge pull request #50288 from frappe/revert-50013-refactor/transporter-filter-buying
Revert "refactor: add supplier filter in buying"
2025-10-31 15:46:38 +05:30
Diptanil Saha
a378d8bcc9 Merge pull request #50291 from diptanilsaha/gh_50256
fix: added validation for default accounts on company
2025-10-31 15:36:04 +05:30
diptanilsaha
4af1ae1470 fix: added validation for default accounts on company 2025-10-31 15:04:10 +05:30
l0gesh29
3fcd8d84ac feat: modify accounting dimension as multiselect field 2025-10-31 14:00:37 +05:30
Mihir Kandoi
7f7dd8d0ba Revert "refactor: add supplier filter in buying" 2025-10-31 14:00:19 +05:30
rohitwaghchaure
c4b46344be Merge pull request #50285 from rohitwaghchaure/fixed-github-50279
fix: Serial No and Batch Traceability report
2025-10-31 13:18:00 +05:30
Rohit Waghchaure
1908858cd5 fix: Serial No and Batch Traceability report 2025-10-31 12:38:39 +05:30
rohitwaghchaure
b5a78b5daf Merge pull request #50276 from rohitwaghchaure/fixed-setup-query-default-inv-acct
fix: only show inventory accounts in the dropdown
2025-10-30 15:56:03 +05:30
Rohit Waghchaure
c5fbebecb8 fix: only show inventory accounts in the dropdown 2025-10-30 15:51:52 +05:30
rohitwaghchaure
f2e721de71 Merge pull request #50273 from rohitwaghchaure/fixed-mps-workspace
fix: added MPS, MRP report in the manufacturing workspace
2025-10-30 12:17:48 +05:30
Rohit Waghchaure
b46fa8510d fix: added MPS, MRP report in the manufacturing workspace 2025-10-30 11:50:03 +05:30
rohitwaghchaure
87346dbf17 Merge pull request #50270 from aerele/support-51237
fix: set valuation rate for rejected serial/batch item
2025-10-30 09:00:14 +05:30
MochaMind
6d9d1ea593 fix: sync translations from crowdin (#50269) 2025-10-29 20:07:42 +00:00
venkat102
d002959d35 test: add unit test for valuation rate on rejected serial/batch item 2025-10-30 00:18:23 +05:30
venkat102
614402cf6c fix: set valuation rate for rejected serial/batch item 2025-10-30 00:16:37 +05:30
MochaMind
b7eb01bd7e fix: sync translations from crowdin (#50242) 2025-10-29 15:33:26 +01:00
rohitwaghchaure
1e54b8ec4c Merge pull request #50264 from rohitwaghchaure/fixed-bulk-edit-bom
fix: allow bulk edit for bill of material items
2025-10-29 18:21:02 +05:30
Rohit Waghchaure
1f74e06791 fix: allow bulk edit for bill of material items 2025-10-29 17:43:58 +05:30
khushi8112
33690975f6 fix: create GL entries via hooks only for submitted assets 2025-10-29 13:06:58 +05:30
barredterra
71db348330 fix: placeholder number 2025-10-29 01:50:07 +01:00
barredterra
72fff5d9ee refactor: error message in budget
improve translatability
2025-10-29 01:34:59 +01:00
dharanidharan2813
cf1d892d60 fix: Payment Terms auto-fetched in Sales Invoice even when automatically_fetch_payment_terms is disabled 2025-10-28 17:57:09 +05:30
rohitwaghchaure
76d7fe452f Merge pull request #50247 from rohitwaghchaure/fixed-incorrect-sabb-report
fix: provision to find and fix incorrect serial and batch bundles
2025-10-28 17:44:00 +05:30
Diptanil Saha
05d4152e43 Merge pull request #50137 from aerele/payment-reco-edit-allocated-amount
fix(payment-reco): recalculate amount based on allocated amount
2025-10-28 17:26:48 +05:30
Rohit Waghchaure
10ad56060c fix: provision to find and fix incorrect serial and batch bundles 2025-10-28 16:47:10 +05:30
Diptanil Saha
5d62908ba9 Merge pull request #50220 from aerele/gross-profit-columns
fix(gross profit): remove customer name from columns
2025-10-28 15:20:27 +05:30
rohitwaghchaure
b1cc11cc38 Merge pull request #50248 from mihir-kandoi/fix-sabb-posting-datetime
fix: missed refactoring code
2025-10-28 14:31:42 +05:30
rohitwaghchaure
9ab70d0a6a Merge pull request #50193 from rohitwaghchaure/feat-item-wise-inventory-account
feat: enable item wise inventory account
2025-10-28 13:49:15 +05:30
Mihir Kandoi
941e924d4d fix: missed refactoring code 2025-10-28 13:44:35 +05:30
Diptanil Saha
d932a67407 Merge pull request #50213 from barredterra/transaction-id-unique
fix(Bank Transaction)!: make transaction ID non-unique
2025-10-28 08:14:08 +05:30
Diptanil Saha
e16c6dc0bb Merge pull request #49599 from KerollesFathy/fix-get-payment-mode-account
fix(accounts): reference get_payment_mode_account correctly in Sales Invoice
2025-10-27 22:19:27 +05:30
ravibharathi656
7ce81127d2 fix: avoid group columns mutation 2025-10-27 17:44:12 +05:30
Mihir Kandoi
80fd9a5b0d Merge pull request #50221 from aerele/fix/stock-recon-barcode-scanner
fix: Pass uom field name to update existing item qty
2025-10-27 17:17:35 +05:30
rohitwaghchaure
55e06a49a1 Merge pull request #50233 from rohitwaghchaure/fixed-performance-issue
fix: optimized the slow query to get the batch-wise available qty
2025-10-27 16:48:31 +05:30
Khushi Rawat
7f714d3262 Merge pull request #50222 from aerele/asset-dep-bal-opening-entries
fix(asset depreciations and balances): showing opening entries
2025-10-27 16:17:19 +05:30
Diptanil Saha
f73db0219a Merge pull request #50039 from aerele/fix/jv-account-currency-no-copy
fix(journal-entry): allow copy account currency when duplicating JV
2025-10-27 14:14:08 +05:30
Bhavan23
76748e4573 fix(journal-entry): allow copy account currency when duplicating JV 2025-10-27 08:21:42 +00:00
Rohit Waghchaure
9c21567309 fix: optimized the slow query to get the batchwise available qty 2025-10-27 13:17:52 +05:30
Diptanil Saha
06d28fed09 Merge pull request #50232 from diptanilsaha/gh_50085
fix(consolidated trial balance): index out of range error
2025-10-27 12:52:15 +05:30
diptanilsaha
67c9fe6532 fix(consolidated trial balance): index out of range error 2025-10-27 12:30:16 +05:30
Diptanil Saha
d868f7706e Merge pull request #50103 from rehanrehman389/fix-overlap-validation
fix: fiscal year overlap validation for company-specific years
2025-10-27 10:37:42 +05:30
MochaMind
2f68235442 fix: sync translations from crowdin (#50228) 2025-10-26 16:59:30 +01:00
MochaMind
e3f44c35b7 chore: update POT file (#50225) 2025-10-26 13:14:55 +01:00
MochaMind
b0fef75bfa fix: sync translations from crowdin (#50201) 2025-10-24 17:41:58 +02:00
ravibharathi656
d3afa67be3 fix(asset depreciations and balances): showing opening entries 2025-10-24 18:32:40 +05:30
Kavin
23d69389ec fix: Pass uom field name to update existing item qty 2025-10-24 18:07:02 +05:30
ravibharathi656
0009925af0 fix(gross profit): remove customer name from columns 2025-10-24 14:09:30 +05:30
Rohit Waghchaure
076407ad70 test: test cases for item wise inventory account 2025-10-24 10:08:44 +05:30
rohitwaghchaure
08941bf742 Merge pull request #50215 from rohitwaghchaure/fixed-valuation-rate
fix: get valuation rate based of previous SLEs for material receipt
2025-10-23 23:59:35 +05:30
Rohit Waghchaure
fa9ef6708f fix: get valuation rate based of previous SLEs for material receipt 2025-10-23 23:49:50 +05:30
Rohit Waghchaure
74192547ce feat: enable item wise inventory account 2025-10-23 22:41:04 +05:30
barredterra
a2a41a0eaa fix(Bank Transaction): make transaction ID non-unique 2025-10-23 17:38:34 +02:00
Khushi Rawat
5b6979c700 Merge pull request #50209 from khushi8112/asset-status-in-list-view
fix: set status to Draft for auto-created assets from Purchase Receipt
2025-10-23 18:31:58 +05:30
khushi8112
20c2cb40d1 fix: set status to Draft for auto-created assets from Purchase Receipt 2025-10-23 17:41:51 +05:30
Deepesh Garg
cbccb67bfb Merge pull request #50204 from deepeshgarg007/add_posting_date_param_for_rev
feat: Add posting date param for reverse GL entries
2025-10-23 13:41:22 +05:30
Deepesh Garg
05e1a737f1 chore: Linting issues 2025-10-23 13:06:48 +05:30
Deepesh Garg
38988bf797 feat: Add posting date param for reverse GL entries 2025-10-23 13:00:15 +05:30
rohitwaghchaure
7f63d100b5 Merge pull request #50200 from rohitwaghchaure/fixed-support-50958-1
fix: stock difference value for adjustment entry
2025-10-22 22:31:44 +05:30
Rohit Waghchaure
fb4c7de86c fix: stock difference value for adjustment entry 2025-10-22 19:17:43 +05:30
Mihir Kandoi
abf81e5217 Merge pull request #50130 from PatrickDEissler/task-no-copy-fix
fix(Task): make Timesheet-dependent fields no_copy
2025-10-22 13:46:10 +05:30
Mihir Kandoi
6de258b4c8 chore: remove print statement (#50151) 2025-10-22 13:43:53 +05:30
Kerolles Fathy
8efcf6cb38 Merge pull request #50153 from KerollesFathy/fix-address-error-when-create-transporter
fix: Address title error when create new transporter on driver
2025-10-22 13:43:13 +05:30
rohitwaghchaure
92891c7e72 Merge pull request #50191 from rohitwaghchaure/fixed-support-51420
fix: sabb missed in the incorrect serial no valuation report
2025-10-22 11:55:20 +05:30
Rohit Waghchaure
b50bac6788 fix: sabb missed in the incorrect serial no valuation report 2025-10-22 11:23:51 +05:30
Mihir Kandoi
e3fcae1c0c Merge pull request #50189 from mihir-kandoi/inward-fix
fix: feat: multiple changes related to subcontracting inward
2025-10-22 00:22:34 +05:30
Mihir Kandoi
9f3cb4b783 fix: feat: multiple changes related to subcontracting inward 2025-10-21 23:51:07 +05:30
MochaMind
d9fa9c6b3d fix: sync translations from crowdin (#50185) 2025-10-21 19:35:13 +02:00
Ahmed AbuKhatwa
f51ed30c23 fix: correct monthly sales history (#50056)
* fix: correct monthly sales history

* fix: correct monthly sales history calculation

* chore: remove unrelated file accidentally committed

---------

Co-authored-by: AhmedAbukhatwa <Ahmedabukhatwa1@gmail.com>
2025-10-21 10:46:53 +05:30
Deepesh Garg
7a91ec3e33 perf: Add index for faster queries (#50175) 2025-10-20 20:42:19 +02:00
MochaMind
de93d266a6 fix: sync translations from crowdin (#50176) 2025-10-20 20:40:41 +02:00
MochaMind
53efd2d718 fix: sync translations from crowdin (#50049) 2025-10-20 01:38:15 +02:00
MochaMind
d854c6cc81 chore: update POT file (#50172) 2025-10-19 23:58:40 +02:00
Sagar Vora
e43c2ac5c1 Merge pull request #50168 from barredterra/account-currency
refactor: simplify expression
2025-10-18 23:48:31 +05:30
barredterra
2de88dadd3 refactor: simplify expression
This expression always evaluates to avalue that is equal to account_currency.
2025-10-18 19:55:57 +02:00
Sagar Vora
81ab15351e chore: remove unused import 2025-10-18 00:08:44 +05:30
Sagar Vora
95f604457d refactor: simplify logic 2025-10-18 00:00:36 +05:30
rohitwaghchaure
63a65838d3 Merge pull request #50156 from rohitwaghchaure/fixed-internal-transfer-issue
fix: internal transfer entry with serial/batch
2025-10-17 21:03:08 +05:30
Rohit Waghchaure
9b4e62a758 fix: internal transfer entry with serial/batch 2025-10-17 20:32:36 +05:30
Sagar Vora
0e026b9ccd fix: handle returns as well 2025-10-17 19:21:24 +05:30
Sagar Vora
0968f435d2 test: some tests to ensure correct discount mapping 2025-10-17 17:32:16 +05:30
Sagar Vora
f4f79d99e4 fix: validate that discount amount cannot exceed total before discount 2025-10-17 17:06:03 +05:30
Sagar Vora
feb62102d9 fix: ensure that additional discount amount is not mapped repeatedly 2025-10-17 17:06:03 +05:30
Abdeali Chharchhoda
1ad4dc9066 chore: remove print statement 2025-10-17 15:56:13 +05:30
rohitwaghchaure
36d422fbb5 Merge pull request #50123 from rohitwaghchaure/fixed-better-validation-message
fix: validation for negative batch
2025-10-17 11:36:31 +05:30
Rohit Waghchaure
f9c8f27586 fix: validation for negative batch 2025-10-17 08:52:41 +05:30
Diptanil Saha
7bd5461704 Merge pull request #50141 from diptanilsaha/fix_st_51018
fix(point-of-sale): render payment methods only when payment component is visible
2025-10-17 01:42:22 +05:30
diptanilsaha
7dc4306640 fix(point-of-sale): render payment methods only payment component is visible 2025-10-17 01:26:31 +05:30
l0gesh29
5a7a6a9bd5 fix: recalculate amount based on allocated amount 2025-10-16 17:03:37 +05:30
Patrick Eissler
2a2e4b5423 fix(Task): make Timesheet-dependent fields no_copy 2025-10-16 08:40:54 +02:00
rehansari26
d59e55fb08 test: replace get_doc with new_doc in fiscal year tests 2025-10-16 11:04:48 +05:30
Rehan Ansari
94ae098854 test: fiscal year overlap validation for company-specific years 2025-10-15 23:33:02 +05:30
Diptanil Saha
182c9fd966 fix: added exception handling on service level agreement apply hook (#50096)
* fix: added exception handling on service level agreement apply hook

* Revert "fix: added exception handling on service level agreement apply hook"

This reverts commit dae93aa96f.

* fix: Ignore missing SLA table during install/uninstall

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2025-10-15 13:27:07 +00:00
rohitwaghchaure
afcd1d68f0 Merge pull request #50113 from rohitwaghchaure/fixed-support-50958
fix: adjustment entry
2025-10-15 15:57:55 +05:30
Rohit Waghchaure
c0851abaee fix: adjustment entry 2025-10-15 15:17:59 +05:30
Yash Chaubey
4ede97ae2b perf: optimize company monthly sales query using date range (#48942)
* perf: optimize company monthly sales query using date range instead of DATE_FORMAT

* perf: optimize company monthly sales query using date range
2025-10-15 12:46:02 +05:30
rohitwaghchaure
1fc5af67be Merge pull request #50095 from aerele/support-50163
fix: preview stock ledger for manual serial and batch values
2025-10-15 11:17:15 +05:30
Mihir Kandoi
3af5a83301 fix(stock): remove filter from query (#50104) 2025-10-14 23:55:43 +05:30
Mihir Kandoi
fa8a988454 Merge pull request #50013 from aerele/refactor/transporter-filter-buying
refactor: add supplier filter in buying
2025-10-14 23:40:52 +05:30
Mihir Kandoi
e0adbb2c01 fix: handle flt conversion for prev_ordered_qty (#50078)
Co-authored-by: Kavin <78342682+kavin0411@users.noreply.github.com>
2025-10-14 23:39:14 +05:30
Rehan Ansari
57aaf34d3e fix: fiscal year overlap validation for company-specific years 2025-10-14 23:38:17 +05:30
Mihir Kandoi
e9538f6d64 fix(stock): remove filter from query 2025-10-14 23:37:45 +05:30
Mihir Kandoi
270520a9fc fix: source warehouse validation in manufacturing entry during subcon… (#50101)
fix: source warehouse validation in manufacturing entry during subcontracting inward
2025-10-14 20:30:31 +05:30
Mihir Kandoi
3fd5aace0a fix: source warehouse validation in manufacturing entry during subcontracting inward 2025-10-14 20:08:22 +05:30
ljain112
0f89e750a7 Merge branch 'develop' into tds-jv 2025-10-14 18:40:58 +05:30
Akhil Narang
f8b50d3ffa fix: sanitize projects field in tasks webform (#50089)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-10-14 13:07:59 +00:00
ljain112
2de9f8f2e2 test: add TDS and TCS calculations for journal entries in Debit and Credit Notes 2025-10-14 17:30:22 +05:30
Kavin
c5f68d0b27 fix: preview stock ledger for manual serial and batch values 2025-10-14 17:14:22 +05:30
rohitwaghchaure
2a7e4c68e5 Merge pull request #50080 from rohitwaghchaure/fixed-stock-query
perf: optimize sql query
2025-10-14 17:13:07 +05:30
Khushi Rawat
cc3660635f Merge pull request #50033 from rehanrehman389/filter-sales-team
fix: filter sales team to show only active individual salespersons
2025-10-14 17:08:56 +05:30
rohitwaghchaure
df1e49ffb4 fix: negative error not throw for backdated entry (#50091) 2025-10-14 17:08:43 +05:30
Rohit Waghchaure
88a947ff4e fix: negative error not throw for backdated entry 2025-10-14 16:48:38 +05:30
Mihir Kandoi
ab0ee8809d Merge pull request #50090 from mihir-kandoi/restore_customer_field
fix: restore customer field
2025-10-14 16:27:41 +05:30
Mihir Kandoi
cf97c1db38 fix: add delivery warehouse filter 2025-10-14 16:08:50 +05:30
Mihir Kandoi
ae816d0c1d fix: restore customer field 2025-10-14 16:02:58 +05:30
Rohit Waghchaure
e7b64175fd perf: optimize sql query 2025-10-14 15:03:42 +05:30
Mihir Kandoi
f2b948a483 feat: subcontracting inward (#47728)
* feat: subcontracting inward

* feat: stock reservation

* feat: subcontracting delivery

* feat: all remaining stuff

* fix: linter errors

* fix: patch

* fix: modify stock entry type validation

* fix: customer provided item cost field mandatory validation

* fix: failing tests

* fix: failing tests

* fix: subcontracting controlller

* refactor: semi final

* refactor: final

* chore: resolve conflicts

* refactor: changes requested

* fix: reservation transfer of extra qty

* fix: consider add cost for customer provided rate field

* test: create test data

* test: subcontracted sales order (partial)

* test: fin

* fix: do not add self RM in DN created from SI

* fix: failing test case

* fix: conflicting function name

* refactor: final changes

* fix: more bugs

* perf: various and major performance improvements

* fix: consider warehouse as well in all queries

* fix: same item code with diff warehouse in manufacture entry

* refactor: readability

* fix: frontend validations

* perf: replace query inside loop with single query

* fix: set additional item flag to true when extra customer provided item is received

* fix: bugs found by coderabbit

* fix: more coderabbit bugs

* fix: add validation to disallow cancellation of manufacturing entry

* perf: use cached values wherever it makes sense

* test: fix redundant insert to child tables

* fix: consider SI return of billed self RM

* fix: bug found by coderabbit

---------

Co-authored-by: Mihir Kandoi <mihirkandoi@Mihirs-MacBook-Air.local>
2025-10-14 15:00:49 +05:30
Mihir Kandoi
9772ca75c4 Merge pull request #50004 from aerele/delivery-note-return 2025-10-14 14:49:04 +05:30
Mihir Kandoi
b452724c1a Merge pull request #50079 from mihir-kandoi/gh-50066
chore: replace broken links with correct ones
2025-10-14 14:26:53 +05:30
Mihir Kandoi
224317d1c9 Merge pull request #49960 from aerele/support-50220
Fix/Support 50220
2025-10-14 14:18:36 +05:30
Kavin
1c586697c7 fix: handle multi uom in reserved qty for production plan 2025-10-14 14:00:59 +05:30
Mihir Kandoi
11be07086f chore: replace broken links with correct ones 2025-10-14 13:55:14 +05:30
rohitwaghchaure
9c014c7ba4 Merge pull request #50072 from aerele/support-50647
fix: swap warehouse labels for return entry
2025-10-14 13:35:33 +05:30
Kavin
77c35ef47f fix: handle flt conversion for prev_ordered_qty 2025-10-14 13:35:06 +05:30
rohitwaghchaure
5cd62ad236 Merge pull request #50073 from aerele/support-50395
fix(stock-entry): fetch empty batch for finished item
2025-10-14 13:34:33 +05:30
rohitwaghchaure
10014b9b79 Merge pull request #50065 from rohitwaghchaure/aerele-support-48884
fix(stock-reconciliation): include inventory dimensions in duplicate validation
2025-10-14 13:32:15 +05:30
rohitwaghchaure
08bd3b348f Merge pull request #50070 from rohitwaghchaure/fixed-support-50227
fix: performance issue by adding index
2025-10-14 13:31:30 +05:30
venkat102
74a7ddf66d fix(stock-entry): fetch empty batch for finished item 2025-10-14 13:19:37 +05:30
Kavin
f0c3f0d0be fix: swap warehouse labels for return entry 2025-10-14 13:14:39 +05:30
Rohit Waghchaure
1afc75b15a fix: performance issue by adding index 2025-10-14 13:02:56 +05:30
rohitwaghchaure
9ccc55decc Merge pull request #50027 from rohitwaghchaure/fixed-reset-raw-materials
fix: reset raw materials considering not available batches
2025-10-14 12:48:43 +05:30
rohitwaghchaure
70c6461cad Merge pull request #50061 from rohitwaghchaure/fixed-support-50717
fix: do reposting of first transfer entry based on item-wh combination
2025-10-14 12:47:55 +05:30
Mihir Kandoi
3d7ac166b7 Merge pull request #49445 from aerele/production-plan-item-filter
fix(production plan): filter sales orders by item
2025-10-14 12:02:05 +05:30
Rohit Waghchaure
c95465cba1 fix: duplicate serial nos 2025-10-14 12:01:46 +05:30
Mihir Kandoi
9b3b2102f8 Merge pull request #49891 from rehanrehman389/fix/empty-create-button
fix: prevent empty Create dropdown when In Process
2025-10-14 11:58:04 +05:30
Mihir Kandoi
ff94438563 Merge pull request #50058 from matteoarosti/fix/warehouse-source-reference
fix: warehouse source reference in production report
2025-10-14 11:52:08 +05:30
Rohit Waghchaure
2f25b445ab fix: do reposting of first transfer entry based on item-wh combination 2025-10-14 11:48:42 +05:30
ruthra kumar
d3014447b6 Merge pull request #50003 from frappe/cache_deletion_check
perf: Only check transaction deletion record once during req/job
2025-10-14 10:05:23 +05:30
matteo.arosti
451651e350 fix: warehouse source reference in production report 2025-10-14 00:27:27 +02:00
Rohit Waghchaure
ec1636db12 fix: reset raw materials considering not available batches 2025-10-13 17:29:52 +05:30
Kavin
af8aa153bf fix: sum quantity instead of requried BOM quantity 2025-10-13 15:13:37 +05:30
l0gesh29
fd9167f2af fix: add GROUP BY for dn_detail and convert SQL query to QB 2025-10-13 11:22:29 +05:30
Bhavan23
3e4846ea3d test(accounts-receivable): add test case to validate report data with party account currency 2025-10-12 20:23:25 +00:00
Bhavan23
752ea7ee7d fix(accounts-receivable): ensure report data with party account currency 2025-10-12 20:23:19 +00:00
Rehan Ansari
2fcd406b18 fix: filter sales team to show only active individual salespersons 2025-10-12 19:01:54 +05:30
venkat102
4b21c2cc46 fix(stock-reconciliation): include inventory dimensions in duplicate validation 2025-10-10 18:47:15 +05:30
manikandan-s-18
108b108d64 refactor: add supplier filter in buying 2025-10-10 17:04:49 +05:30
l0gesh29
1f831d8783 fix: hide sales invoice creation for fully returned delivery notes 2025-10-10 13:31:57 +05:30
Ankush Menat
8d723d3da6 perf: Only check transaction deletion record once during req/job 2025-10-10 12:44:33 +05:30
ljain112
88f6d783b4 fix: include grand_total in journal entry and handle taxes correctly in invoice total calculation 2025-10-09 13:44:32 +05:30
ljain112
2112f36577 fix: add cost center to tds row in journal entry 2025-10-09 13:32:45 +05:30
ljain112
610877fb17 refactor: update exchange rate import to avoid redundancy 2025-10-09 13:11:10 +05:30
ljain112
004bd59245 fix: calculate net_total excluding taxes 2025-10-09 13:08:55 +05:30
ljain112
84e6d278c3 chore: remove redundant code 2025-10-09 12:54:55 +05:30
ljain112
2b4f621c8e refactor: proper variable naming 2025-10-09 12:24:54 +05:30
ljain112
31434630b5 fix: handle multicurrency in tds jv 2025-10-08 19:29:39 +05:30
ljain112
47aa006ea9 fix: recalculate totals after applying tds 2025-10-08 18:02:19 +05:30
ljain112
1319b28b1f fix: tds for customer and supplier in Journal Entry 2025-10-08 17:27:58 +05:30
Rehan Ansari
0948358bb3 fix: prevent empty Create dropdown when In Process 2025-10-06 01:29:54 +05:30
KerollesFathy
0af74aef00 fix(accounts): update payment mode account retrieval to use namespaced function 2025-09-17 13:14:35 +00:00
ravibharathi656
bfff945fb1 fix(production plan): filter sales orders by item 2025-09-03 16:54:13 +05:30
Anjali Patel
cbfb14a654 fix: add missing query key in 'Reports To' field filter 2025-08-13 13:37:48 +00:00
Anjali Patel
608d38a172 fix: prevent self in "Reports To" dropdown (UI-level check)
Ensures employee cannot select themselves in the "Reports To" field via UI.
This complements server-side validation by improving UX.
2025-08-13 12:25:22 +00:00
1910 changed files with 601815 additions and 476493 deletions

View File

@@ -45,3 +45,9 @@ d827ed21adc7b36047e247cbb0dc6388d048a7f9
# `frappe.flags.in_test` => `frappe.in_test`
7a482a69985c952de0e8193c9d4e086aee65ee6d
# these commits actually changed something valuable
# but they have a lot of whitespace changes that make blame noisy
# PR: https://github.com/frappe/erpnext/pull/49816
3ffd50c772735877b330d010c1058f623da8721d
0e8f8677b8eb31e7834f72d1c6314d3c3f392ca6

View File

@@ -1,36 +1,70 @@
### Introduction (first timers)
### Introduction (For First-Time Contributors)
Thank you for your interest in raising an Issue with ERPNext. An Issue could mean a bug report or a request for a missing feature. By raising a bug report, you are contributing to the development of ERPNext and this is the first step of participating in the community. Bug reports are very helpful for developers as they quickly fix the issue before other users start facing it.
Thank you for your interest in raising an issue with ERPNext. An issue can be either a bug report or a feature request.
Feature requests are also a great way to take the product forward. New ideas can come in any user scenario and the issue list also acts a roadmap of future features.
By reporting bugs, you contribute directly to improving ERPNext. Bug reports help developers identify and fix issues quickly before they affect more users.
When you are raising an Issue, you should keep a few things in mind. Remember that the developer does not have access to your machine so you must give all the information you can while raising an Issue. If you are suggesting a feature, you should be very clear about what you want.
Feature requests are also valuable. They help shape the future of the product by introducing new ideas and improvements based on real-world use cases.
The Issue list is not the right place to ask a question or start a general discussion. If you want to do that , then the right place is the forum [https://discuss.erpnext.com](https://discuss.erpnext.com).
When raising an issue, keep in mind that developers do not have access to your environment. Therefore, provide as much relevant information as possible.
If you are suggesting a feature, clearly describe what you expect and how it should behave.
> ⚠️ The issue tracker is not the right place for general questions or discussions.
> Please use the forum instead: https://discuss.frappe.io/c/erpnext/6
---
### Reply and Closing Policy
If your issue is not clear or does not meet the guidelines, then it will be closed. If it is closed, please supply the information asked and re-open it.
If your issue is unclear or does not meet the guidelines, it may be closed.
If that happens, please provide the requested information and reopen the issue.
---
### General Issue Guidelines
1. **Search existing Issues:** Before raising a Issue, search if it has been raised before. Maybe add a 👍 or give additional help by creating a mockup if it is not already created.
1. **Report each issue separately:** Don't club multiple, unreleated issues in one note.
1. **Brief:** Please don't include long explanations. Use screenshots and bullet points instead of descriptive paragraphs.
1. **Search existing issues:**
Before creating a new issue, check if it already exists. You can support existing issues with a 👍 or contribute additional details or mockups.
2. **Report issues separately:**
Do not combine multiple unrelated issues into a single report.
3. **Be concise:**
Avoid long explanations. Use bullet points and screenshots where possible.
---
### Bug Report Guidelines
1. **Steps to Reproduce:** The bug report must have a list of steps needed to reproduce a bug. If we cannot reproduce it, then we cannot solve it.
1. **Version Number:** Please add the version number in your report. Often a bug is fixed in the latest version
1. **Clear Title:** Add a clear subject to your bug report like "Unable to submit Purchase Order without Basic Rate" instead of just "Cannot Submit"
1. **Screenshots:** Screenshots are a great way of communicating issues. Try adding annotations or using LiceCAP to take a screencast in `gif`.
1. **Steps to reproduce:**
Clearly list the steps required to reproduce the issue. If the issue cannot be reproduced, it cannot be fixed.
2. **Version number:**
Include the ERPNext version. The issue may already be fixed in a newer release.
3. **Clear title:**
Use a descriptive title (e.g., "Unable to submit Purchase Order without Basic Rate" instead of "Cannot submit").
4. **Screenshots:**
Add screenshots or screen recordings (e.g., `.gif`) to illustrate the issue.
---
### Feature Request Guidelines
1. **Clarity:** Clearly specify how do you want the feature to behave. Don't just say "I would like multiple PDF formats", say that "Ability to add multiple print formats for customers with different languages".
1. **Solution:** Try and identify how the feature should look like.
1. **Mockups:** Mockups are a great way to explain your requirement.
1. **Clarity:**
Clearly describe the expected behavior. Avoid vague statements.
### What if my Issue is closed
2. **Proposed solution:**
Suggest how the feature should work.
Don't worry, take the feedback, supply the correct information and re-open it!
3. **Mockups:**
Provide mockups or examples whenever possible.
---
### What if my issue is closed?
Don't worry. Review the feedback, provide the required information, and reopen the issue.

View File

@@ -9,7 +9,7 @@ body:
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
- For questions and general support, checkout the [user manual](https://docs.erpnext.com/) or use [forum](https://discuss.erpnext.com)
- For questions and general support, checkout the [user manual](https://docs.erpnext.com/) or use [forum](https://discuss.frappe.io/c/erpnext/6)
- For documentation issues, propose edit on [documentation site](https://docs.erpnext.com/) directly.
2. When making a bug report, make sure you provide all required information. The easier it is for
maintainers to reproduce, the faster it'll be fixed.
@@ -60,7 +60,7 @@ body:
description: Share exact version number of Frappe and ERPNext you are using.
placeholder: |
Frappe version -
ERPNext Verion -
ERPNext version -
validations:
required: true

View File

@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Community Forum
url: https://discuss.erpnext.com/
url: https://discuss.frappe.io/c/erpnext/6
about: For general QnA, discussions and community help.

View File

@@ -1,6 +1,6 @@
---
name: Feature request
about: Suggest an idea to improve ERPNext
about: Suggest an idea or enhancement for ERPNext
title: ''
labels: feature-request
assignees: ''
@@ -11,23 +11,27 @@ assignees: ''
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
- For questions and general support, checkout the manual https://erpnext.com/docs/user/manual/en or use https://discuss.erpnext.com
- For questions and general support, checkout the manual https://docs.erpnext.com or use https://discuss.frappe.io/c/erpnext/6
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
the original discussion.
3. When making a feature request, make sure to be as verbose as possible. The better you convey your message, the greater the drive to make it happen.
Please keep in mind that we get many many requests and we can't possibly work on all of them, we prioritize development based on the goals of the product and organization. Feature requests are still welcome as it helps us in research when we do decide to work on the requested feature.
Please keep in mind that we get many requests and we can't possibly work on all of them, we prioritize development based on the goals of the product and organization. Feature requests are still welcome as it helps us in research when we do decide to work on the requested feature.
If you're in urgent need to a feature, please try the following channels to get paid developments done quickly:
If you're in urgent need of a feature, please try the following channels to get paid developments done quickly:
1. Certified ERPNext partners: https://erpnext.com/partners
2. Developer community on ERPNext forums: https://discuss.erpnext.com/c/developers/5
2. Developer community on ERPNext forums: https://discuss.frappe.io/c/framework/5
3. Telegram group for ERPNext/Frappe development work: https://t.me/erpnext_opps
-->
## Before Submitting
- [ ] I searched existing issues and confirmed this is not a duplicate
- [ ] This is a feature request, not a bug or support question
- [ ] For support: https://discuss.frappe.io/c/erpnext/6
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
A clear and concise description of what the problem is. Ex. As a [role], I have to [painful task] because [missing feature].
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
@@ -35,5 +39,17 @@ A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Impact**
<!-- Check one: -->
- [ ] Blocks critical workflow — no viable workaround
- [ ] Significant friction — workaround exists but is painful
- [ ] Nice to have — minor improvement
**Additional context**
Add any other context or screenshots about the feature request here.
**Environment**
- ERPNext Version: <!-- Find this in Help > About, e.g. v15.12.0 -->
- Frappe Version: <!-- Find this in Help > About, e.g. v15.10.0 -->
- Deployment: <!-- Frappe Cloud / Self-hosted / ERPNext Cloud -->

View File

@@ -14,7 +14,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout Actions
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
repository: "frappe/backport"
path: ./actions

View File

@@ -13,12 +13,12 @@ jobs:
steps:
- name: 'Setup Environment'
uses: actions/setup-python@v2
uses: actions/setup-python@v6
with:
python-version: '3.10'
- name: 'Clone repo'
uses: actions/checkout@v2
uses: actions/checkout@v6
- name: Validate Docs
env:

View File

@@ -15,20 +15,25 @@ jobs:
strategy:
fail-fast: false
matrix:
branch: ["develop"]
branch: ["develop", "version-16-hotfix"]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
ref: ${{ matrix.branch }}
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.12"
python-version: "3.14"
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
- name: Run script to update POT file
run: |

View File

@@ -19,7 +19,7 @@ jobs:
strategy:
fail-fast: false
matrix:
version: ["14", "15"]
version: ["15", "16"]
steps:
- uses: octokit/request-action@v2.x

View File

@@ -12,12 +12,12 @@ jobs:
name: linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
- name: Set up Python 3.10
uses: actions/setup-python@v4
- name: Set up Python 3.14
uses: actions/setup-python@v6
with:
python-version: '3.10'
python-version: '3.14'
cache: pip
- name: Install and Run Pre-commit
@@ -27,12 +27,12 @@ jobs:
name: semgrep
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
- name: Set up Python 3.10
uses: actions/setup-python@v4
- name: Set up Python 3.14
uses: actions/setup-python@v6
with:
python-version: '3.10'
python-version: '3.14'
cache: pip
- name: Download Semgrep rules
@@ -43,3 +43,6 @@ jobs:
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
- name: Semgrep for Test Correctness
run: semgrep ci --include=**/test_*.py --config ./semgrep/test-correctness.yml

View File

@@ -29,7 +29,7 @@ jobs:
services:
mysql:
image: mariadb:10.6
image: mariadb:11.8
env:
MARIADB_ROOT_PASSWORD: 'root'
ports:
@@ -38,7 +38,7 @@ jobs:
steps:
- name: Clone
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Check for valid Python & Merge Conflicts
run: |
@@ -49,14 +49,17 @@ jobs:
fi
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.11'
python-version: |
3.11
3.13
3.14
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 18
node-version: 24
check-latest: true
- name: Add to Hosts
@@ -110,8 +113,8 @@ jobs:
jq 'del(.install_apps)' ~/frappe-bench/sites/test_site/site_config.json > tmp.json
mv tmp.json ~/frappe-bench/sites/test_site/site_config.json
wget https://erpnext.com/files/v13-erpnext.sql.gz
bench --site test_site --force restore ~/frappe-bench/v13-erpnext.sql.gz
wget https://frappe.io/files/erpnext-v14.sql.gz
bench --site test_site --force restore ~/frappe-bench/erpnext-v14.sql.gz
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
@@ -132,15 +135,15 @@ jobs:
# Resetup env and install apps
pgrep honcho | xargs kill
rm -rf ~/frappe-bench/env
bench -v setup env
bench -v setup env --python python$2
bench pip install -e ./apps/erpnext
bench start &>> ~/frappe-bench/bench_start.log &
bench --site test_site migrate
}
update_to_version 14
update_to_version 15
update_to_version 15 3.13
update_to_version 16 3.14
echo "Updating to latest version"
git -C "apps/frappe" fetch --depth 1 upstream "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"

View File

@@ -13,12 +13,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Entire Repository
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v6
with:
node-version: 20
- name: Setup dependencies

View File

@@ -4,8 +4,8 @@ on:
workflow_dispatch:
concurrency:
group: server-individual-tests-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
cancel-in-progress: false
group: server-individual-tests-lightmode-develop
cancel-in-progress: true
permissions:
contents: read
@@ -17,11 +17,11 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Clone
uses: actions/checkout@v4
uses: actions/checkout@v6
- id: set-matrix
run: |
# Use grep and find to get the list of test files
matrix=$(find . -path '*/doctype/*/test_*.py' | xargs grep -l 'def test_' | awk '{
matrix=$(find . -path '*/test_*.py' | xargs grep -l 'def test_' | sort | awk '{
# Remove ./ prefix, file extension, and replace / with .
gsub(/^\.\//, "", $0)
gsub(/\.py$/, "", $0)
@@ -58,6 +58,7 @@ jobs:
strategy:
fail-fast: false
matrix: ${{fromJson(needs.discover.outputs.matrix)}}
max-parallel: 14
name: Test
@@ -72,17 +73,17 @@ jobs:
steps:
- name: Clone
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.12'
python-version: '3.14'
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 18
node-version: 24
check-latest: true
- name: Add to Hosts
@@ -130,4 +131,13 @@ jobs:
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
- name: Run Tests
run: 'cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --module ${{ matrix.test }}'
run: |
site_name=$(echo "${{matrix.test}}" | sed -e 's/.*\.\(test_.*$\)/\1/')
echo "$site_name"
mkdir ~/frappe-bench/sites/$site_name
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config_mariadb.json" ~/frappe-bench/sites/$site_name/site_config.json
cd ~/frappe-bench/
bench --site $site_name reinstall --yes
bench --site $site_name set-config allow_tests true
bench --site $site_name run-tests --module ${{ matrix.test }} --lightmode

View File

@@ -15,11 +15,11 @@ jobs:
name: Check Commit Titles
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
fetch-depth: 200
- uses: actions/setup-node@v3
- uses: actions/setup-node@v6
with:
node-version: 18
check-latest: true

View File

@@ -7,6 +7,7 @@ on:
paths:
- "**.js"
- "**.css"
- "**.svg"
- "**.md"
- "**.html"
- 'crowdin.yml'

View File

@@ -7,6 +7,7 @@ on:
paths-ignore:
- '**.js'
- '**.css'
- '**.svg'
- '**.md'
- '**.html'
- 'crowdin.yml'
@@ -40,6 +41,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
TZ: 'Asia/Kolkata'
NODE_ENV: "production"
WITH_COVERAGE: ${{ github.event_name != 'pull_request' }}
@@ -55,6 +57,7 @@ jobs:
mysql:
image: mariadb:10.6
env:
TZ: 'Asia/Kolkata'
MARIADB_ROOT_PASSWORD: 'root'
ports:
- 3306:3306
@@ -62,12 +65,12 @@ jobs:
steps:
- name: Clone
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.12'
python-version: '3.14'
- name: Check for valid Python & Merge Conflicts
run: |
@@ -78,9 +81,9 @@ jobs:
fi
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 18
node-version: 24
check-latest: true
- name: Add to Hosts
@@ -128,10 +131,9 @@ jobs:
FRAPPE_BRANCH: ${{ github.event.client_payload.sha || github.event.inputs.branch }}
- name: Run Tests
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds ${{ strategy.job-total }} --build-number ${{ matrix.container }}'
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --lightmode --app erpnext --total-builds ${{ strategy.job-total }} --build-number ${{ matrix.container }} --with-coverage'
env:
TYPE: server
CAPTURE_COVERAGE: ${{ github.event_name != 'pull_request' }}
- name: Show bench output
@@ -140,7 +142,6 @@ jobs:
- name: Upload coverage data
uses: actions/upload-artifact@v4
if: github.event_name != 'pull_request'
with:
name: coverage-${{ matrix.container }}
path: /home/runner/frappe-bench/sites/coverage.xml
@@ -149,10 +150,9 @@ jobs:
name: Coverage Wrap Up
needs: test
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Clone
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v4

View File

@@ -47,12 +47,12 @@ jobs:
steps:
- name: Clone
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.12'
python-version: '3.14'
- name: Check for valid Python & Merge Conflicts
run: |
@@ -63,9 +63,9 @@ jobs:
fi
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 18
node-version: 24
check-latest: true
- name: Add to Hosts

4
.gitignore vendored
View File

@@ -19,3 +19,7 @@ node_modules/
.backportrc.json
# Aider AI Chat
.aider*
# Banking SPA
erpnext/public/banking
erpnext/www/banking.html

View File

@@ -50,6 +50,15 @@ pull_request_rules:
- version-15-hotfix
assignees:
- "{{ author }}"
- name: backport to version-16-hotfix
conditions:
- label="backport version-16-hotfix"
actions:
backport:
branches:
- version-16-hotfix
assignees:
- "{{ author }}"
- name: Automatic merge on CI success and review
conditions:
- status-success=linters

View File

@@ -7,17 +7,17 @@ erpnext/accounts/ @ruthra-kumar
erpnext/assets/ @khushi8112
erpnext/regional @ruthra-kumar
erpnext/selling @ruthra-kumar
erpnext/support/ @ruthra-kumar
erpnext/buying/ @rohitwaghchaure @mihir-kandoi
erpnext/maintenance/ @rohitwaghchaure
erpnext/maintenance/ @rohitwaghchaure @mihir-kandoi
erpnext/manufacturing/ @rohitwaghchaure @mihir-kandoi
erpnext/quality_management/ @rohitwaghchaure
erpnext/quality_management/ @rohitwaghchaure @mihir-kandoi
erpnext/stock/ @rohitwaghchaure @mihir-kandoi
erpnext/subcontracting @mihir-kandoi
erpnext/subcontracting/ @mihir-kandoi
erpnext/projects/ @nishkagosalia
erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
erpnext/patches/ @ruthra-kumar
erpnext/patches/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
.github/ @ruthra-kumar
.github/ @ruthra-kumar @mihir-kandoi
pyproject.toml @ruthra-kumar

View File

@@ -1,20 +1,21 @@
<div align="center">
<a href="https://frappe.io/erpnext">
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80xp"/>
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80px"/>
</a>
<h2>ERPNext</h2>
<p align="center">
<div align="center">
<p>Powerful, Intuitive and Open-Source ERP</p>
</p>
</div>
[![Learn on Frappe School](https://img.shields.io/badge/Frappe%20School-Learn%20ERPNext-blue?style=flat-square)](https://frappe.school)<br><br>
[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml/badge.svg?event=schedule)](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml)
[![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker)
[![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext.svg)](https://hub.docker.com/r/frappe/erpnext)
</div>
<div align="center">
<img src="./erpnext/public/images/v16/hero_image.png"/>
<img src="./erpnext/public/images/v16/hero_image.png" alt="ERPNext Hero Image"/>
</div>
<div align="center">
@@ -27,19 +28,19 @@
## ERPNext
100% Open-Source ERP system to help you run your business.
100% Open-Source ERP System to help you run your business.
### Motivation
Running a business is a complex task - handling invoices, tracking stock, managing personnel and even more ad-hoc activities. In a market where software is sold separately to manage each of these tasks, ERPNext does all of the above and more, for free.
Running a business is a complex task - handling invoices, tracking stock, managing personnel, and other daily operations. In a market where software is sold separately to manage each of these tasks, ERPNext does all of the above and more, for free.
### Key Features
- **Accounting**: All the tools you need to manage cash flow in one place, right from recording transactions to summarizing and analyzing financial reports.
- **Order Management**: Track inventory levels, replenish stock, and manage sales orders, customers, suppliers, shipments, deliverables, and order fulfillment.
- **Manufacturing**: Simplifies the production cycle, helps track material consumption, exhibits capacity planning, handles subcontracting, and more!
- **Asset Management**: From purchase to perishment, IT infrastructure to equipment. Cover every branch of your organization, all in one centralized system.
- **Projects**: Delivery both internal and external Projects on time, budget and Profitability. Track tasks, timesheets, and issues by project.
- **Asset Management**: From purchase to disposal, IT infrastructure to equipment. Covers every branch of your organization, all in one centralized system.
- **Projects**: Deliver both internal and external projects on time, budget and profitability. Track tasks, timesheets, and issues by project.
<details open>
@@ -52,7 +53,7 @@ Running a business is a complex task - handling invoices, tracking stock, managi
### Under the Hood
- [**Frappe Framework**](https://github.com/frappe/frappe): A full-stack web application framework written in Python and Javascript. The framework provides a robust foundation for building web applications, including a database abstraction layer, user authentication, and a REST API.
- [**Frappe Framework**](https://github.com/frappe/frappe): A full-stack web application framework written in Python and JavaScript. The framework provides a robust foundation for building web applications, including a database abstraction layer, user authentication, and a REST API.
- [**Frappe UI**](https://github.com/frappe/frappe-ui): A Vue-based UI library, to provide a modern user interface. The Frappe UI library provides a variety of components that can be used to build single-page applications on top of the Frappe Framework.
@@ -60,12 +61,12 @@ Running a business is a complex task - handling invoices, tracking stock, managi
### Managed Hosting
You can try [Frappe Cloud](https://frappecloud.com), a simple, user-friendly and sophisticated [open-source](https://github.com/frappe/press) platform to host Frappe applications with peace of mind.
You can try [Frappe Cloud](https://frappecloud.com), a simple, user-friendly, and sophisticated [open-source](https://github.com/frappe/press) platform to host Frappe applications reliably and securely.
It takes care of installation, setup, upgrades, monitoring, maintenance and support of your Frappe deployments. It is a fully featured developer platform with an ability to manage and control multiple Frappe deployments.
It handles installation, setup, upgrades, monitoring, maintenance, and support of your Frappe deployments. It is a fully featured developer platform with an ability to manage and control multiple Frappe deployments.
<div>
<a href="https://erpnext-demo.frappe.cloud/app/home" target="_blank">
<a href="https://erpnext-demo.frappe.cloud/app/home" target="_blank" rel="noopener noreferrer">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/try-on-fc-white.png">
<img src="https://frappe.io/files/try-on-fc-black.png" alt="Try on Frappe Cloud" height="28" />
@@ -74,25 +75,40 @@ It takes care of installation, setup, upgrades, monitoring, maintenance and supp
</div>
### Self-Hosted
#### Docker
Prerequisites: docker, docker-compose, git. Refer [Docker Documentation](https://docs.docker.com) for more details on Docker setup.
See [Frappe Docker Documentation](https://github.com/frappe/frappe_docker) for full documentation & FAQ on Docker setup
Run following commands:
#### Prerequisites
```
- [Docker](https://docs.docker.com/get-docker/)
- [Docker Compose v2](https://docs.docker.com/compose/)
- [git](https://docs.github.com/en/get-started/getting-started-with-git/set-up-git)
> For Docker basics and best practices refer to Docker's [documentation](https://docs.docker.com)
### Try on your environment
> **⚠️ Disposable demo only**
>
> **This setup is intended for quick evaluation. Expect to throw the environment away.** You will not be able to install custom apps to this setup. For production deployments, custom configurations, and detailed explanations, see the full documentation.
First clone the repo:
```sh
git clone https://github.com/frappe/frappe_docker
cd frappe_docker
docker compose -f pwd.yml up -d
```
After a couple of minutes, site should be accessible on your localhost port: 8080. Use below default login credentials to access the site.
- Username: Administrator
- Password: admin
Then run:
See [Frappe Docker](https://github.com/frappe/frappe_docker?tab=readme-ov-file#to-run-on-arm64-architecture-follow-this-instructions) for ARM based docker setup.
```sh
docker compose -f pwd.yml up -d
```
Wait for a couple of minutes for ERPNext site to be created or check the `create-site` container logs before opening browser on port `8080`. (username: `Administrator`, password: `admin`)
See [Frappe Docker](https://github.com/frappe/frappe_docker/blob/main/docs/01-getting-started/03-arm64.md) for ARM based docker setup
## Development Setup
@@ -100,7 +116,7 @@ See [Frappe Docker](https://github.com/frappe/frappe_docker?tab=readme-ov-file#t
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the Frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
### Local
@@ -129,20 +145,20 @@ To setup the repository locally follow the steps mentioned below:
4. Open the URL `http://erpnext.localhost:8000/app` in your browser, you should see the app running
## Learning and community
## Learning and Community
1. [Frappe School](https://school.frappe.io) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext.
3. [Discussion Forum](https://discuss.erpnext.com/) - Engage with community of ERPNext users and service providers.
3. [Discussion Forum](https://discuss.frappe.io/c/erpnext/6) - Engage with the community of ERPNext users and service providers.
4. [Telegram Group](https://erpnext_public.t.me) - Get instant help from huge community of users.
## Contributing
1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
1. [Report Security Vulnerabilities](https://erpnext.com/security)
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
2. [Translations](https://crowdin.com/project/frappe)
2. [Report Security Vulnerabilities](https://erpnext.com/security)
3. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
4. [Translations](https://crowdin.com/project/frappe)
## Logo and Trademark Policy

View File

@@ -1,7 +1,7 @@
# Security Policy
The ERPNext team and community take security issues seriously. To report a security issue, fill out the form at [https://erpnext.com/security/report](https://erpnext.com/security/report).
The ERPNext team and community take security issues seriously. To report a security issue, please go through the information mentioned [here](https://frappe.io/security).
You can help us make ERPNext and all it's users more secure by following the [Reporting guidelines](https://erpnext.com/security).
You can help us make ERPNext and all its users more secure by following the [Reporting guidelines](https://frappe.io/security).
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.

View File

@@ -18,8 +18,9 @@ We will grant permission to use the ERPNext name and logo for projects that meet
- The primary purpose of your project is to promote the spread and improvement of the ERPNext software.
- Your project is non-commercial in nature (it can make money to cover its costs or contribute to non-profit entities, but it cannot be run as a for-profit project or business).
Your project neither promotes nor is associated with entities that currently fail to comply with the GPL license under which ERPNext is distributed.
- If your project meets these criteria, you will be permitted to use the ERPNext name and logo to promote your project in any way you see fit with one exception: Please do not use ERPNext as part of a domain name.
- Your project neither promotes nor is associated with entities that currently fail to comply with the GPL license under which ERPNext is distributed.
If your project meets these criteria, you will be permitted to use the ERPNext name and logo to promote your project in any way you see fit with one exception: Please do not use ERPNext as part of a domain name.
Use of the ERPNext name and logo is additionally allowed in the following situations:

View File

@@ -1,3 +1,5 @@
**/setup/setup_wizard/data/uom_data.json,erpnext.gettext.extractors.uom_data.extract
**/setup/doctype/incoterm/incoterms.csv,erpnext.gettext.extractors.incoterms.extract
**/setup/setup_wizard/data/*.txt,erpnext.gettext.extractors.lines_from_txt_file.extract
**.tsx,frappe.gettext.extractors.html_template.extract
**.ts,frappe.gettext.extractors.html_template.extract
1 **/setup/setup_wizard/data/uom_data.json erpnext.gettext.extractors.uom_data.extract
2 **/setup/doctype/incoterm/incoterms.csv erpnext.gettext.extractors.incoterms.extract
3 **/setup/setup_wizard/data/*.txt erpnext.gettext.extractors.lines_from_txt_file.extract
4 **.tsx frappe.gettext.extractors.html_template.extract
5 **.ts frappe.gettext.extractors.html_template.extract

1
banking/.env.production Normal file
View File

@@ -0,0 +1 @@
VITE_BASE_NAME="banking"

24
banking/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# 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?

73
banking/README.md Normal file
View File

@@ -0,0 +1,73 @@
# 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) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
## 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 defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// 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,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
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 defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```

24
banking/eslint.config.js Normal file
View File

@@ -0,0 +1,24 @@
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";
import { defineConfig, globalIgnores } from "eslint/config";
export default defineConfig([
globalIgnores(["dist"]),
{
files: ["**/*.{ts,tsx}"],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
onlyExportComponents: false,
},
]);

50
banking/index.html Normal file
View File

@@ -0,0 +1,50 @@
<!doctype html>
<html lang="{{ lang }}" dir="{{layout_direction}}">
<head>
<meta charset="UTF-8" />
<!-- Chrome, Firefox OS and Opera -->
<meta name="theme-color" content="#0089FF">
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#0089FF">
<!-- iOS Safari -->
<meta name="apple-mobile-web-app-status-bar-style" content="#0089FF">
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, minimal-ui">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="white">
<meta name="mobile-web-app-capable" content="yes">
<link rel="shortcut icon" href="{{ favicon or ' /assets/erpnext/images/erpnext-favicon.svg' }}" type="image/x-icon">
<link rel="icon" href="{{ favicon or ' /assets/erpnext/images/erpnext-favicon.svg' }}" type="image/x-icon">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Banking | {{ app_name }}</title>
</head>
<body>
<div id="root"></div>
<script>window.csrf_token = '{{ frappe.session.csrf_token }}';
if (!window.frappe) window.frappe = {};
frappe.boot = JSON.parse({{ boot }});
frappe.boot.layout_direction = "{{ layout_direction }}";
frappe._translations_loaded = fetch(
`/api/method/frappe.translate.get_boot_translations?v=${frappe.boot.translations_version}&lang=${frappe.boot.lang}`,
{
credentials: "same-origin",
headers: {
"X-Frappe-CSRF-Token": frappe.csrf_token,
"Accept": "application/json"
}
}
).then(r => r.json()).then(data => {
frappe._messages = data.message || {};
}).catch(() => { });
</script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

66
banking/package.json Normal file
View File

@@ -0,0 +1,66 @@
{
"name": "banking",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --base=/assets/erpnext/banking/ && yarn copy-html-entry",
"lint": "eslint .",
"preview": "vite preview",
"copy-html-entry": "cp ../erpnext/public/banking/index.html ../erpnext/www/banking.html"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@tailwindcss/vite": "^4.3.0",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.24",
"@vitejs/plugin-react": "^6.0.1",
"chrono-node": "^2.9.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"dayjs": "^1.11.20",
"frappe-react-sdk": "^1.15.0",
"fuse.js": "^7.3.0",
"jotai": "^2.20.0",
"jotai-family": "^1.0.1",
"lodash.isplainobject": "^4.0.6",
"lucide-react": "^1.14.0",
"radix-ui": "^1.4.3",
"react": "^19.2.6",
"react-currency-input-field": "^4.0.5",
"react-day-picker": "9.14.0",
"react-dom": "^19.2.6",
"react-dropzone": "^15.0.0",
"react-hook-form": "^7.75.0",
"react-hotkeys-hook": "^5.3.2",
"react-markdown": "^10.1.0",
"react-router": "^7.15.0",
"react-router-dom": "^7.15.0",
"react-virtuoso": "^4.18.6",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.3.0",
"tw-animate-css": "^1.4.0",
"usehooks-ts": "^3.1.1",
"vite": "^8.0.11"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^25.3.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.48.0"
}
}

13
banking/proxyOptions.ts Normal file
View File

@@ -0,0 +1,13 @@
const common_site_config = require('../../../sites/common_site_config.json');
const { webserver_port } = common_site_config;
export default {
'^/(app|api|assets|files|private)': {
target: `http://127.0.0.1:${webserver_port}`,
ws: true,
router: function(req) {
const site_name = req.headers.host.split(':')[0];
return `http://${site_name}:${webserver_port}`;
}
}
};

65
banking/src/App.tsx Normal file
View File

@@ -0,0 +1,65 @@
import { useEffect } from 'react'
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'
import { FrappeProvider } from 'frappe-react-sdk'
import { Toaster } from '@/components/ui/sonner'
import BankReconciliation from '@/pages/BankReconciliation'
import { TooltipProvider } from './components/ui/tooltip'
import BankStatementImporter from '@/pages/BankStatementImporter'
import { LucideProvider } from 'lucide-react'
import { ThemeProvider } from './components/ui/theme-provider'
import ViewBankStatementImportLog from './pages/ViewBankStatementImportLog'
import BankStatementImporterContainer from './pages/BankStatementImporterContainer'
function App() {
useEffect(() => {
// Check if user is logged in by checking the Cookie "user_id"
// In Frappe, unauthenticated users are "Guest"
const userId = document.cookie?.split('; ').find(row => row.startsWith('user_id='))?.split('=')[1]?.trim()
const isLoggedIn = userId !== 'Guest'
if (!isLoggedIn) {
if (import.meta.env.DEV) {
return
}
// Redirect to Frappe login page
window.location.href = '/login?redirect-to=/banking'
return
}
}, [])
return (
<LucideProvider
strokeWidth={1.5}
>
<TooltipProvider>
<FrappeProvider
swrConfig={{
errorRetryCount: 2
}}
socketPort={import.meta.env.VITE_SOCKET_PORT}
siteName={window.frappe?.boot?.sitename ?? import.meta.env.VITE_SITE_NAME}>
<ThemeProvider
defaultTheme={window.frappe?.boot?.desk_theme ?? "Automatic"}
>
{window.frappe?.boot?.user?.name && window.frappe?.boot?.user?.name !== 'Guest' &&
<BrowserRouter basename={import.meta.env.VITE_BASE_NAME ? `/${import.meta.env.VITE_BASE_NAME}` : ''}>
<Routes>
<Route index element={<BankReconciliation />} />
<Route path="/statement-importer" element={<BankStatementImporterContainer />}>
<Route index element={<BankStatementImporter />} />
<Route path=":id" element={<ViewBankStatementImportLog />} />
</Route>
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</BrowserRouter>
}
<Toaster richColors />
</ThemeProvider>
</FrappeProvider>
</TooltipProvider>
</LucideProvider>
)
}
export default App

View File

@@ -0,0 +1,228 @@
import { Button } from "@/components/ui/button"
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { useCurrentCompany } from "@/hooks/useCurrentCompany"
import _ from "@/lib/translate"
import { cn } from "@/lib/utils"
import { useFrappeGetDocList } from "frappe-react-sdk"
import Fuse from "fuse.js"
import { ChevronDownIcon } from "lucide-react"
import { useLayoutEffect, useMemo, useRef, useState } from "react"
import { FormControl } from "../ui/form"
export interface AccountsDropdownProps {
root_type?: ('Asset' | 'Liability' | 'Equity' | 'Income' | 'Expense')[],
report_type?: 'Balance Sheet' | 'Profit and Loss',
account_type?: string[],
value?: string,
onChange?: (value: string) => void,
readOnly?: boolean,
disabled?: boolean,
company?: string,
filterFunction?: (account: Account) => boolean,
// If true, the component will be wrapped in a FormControl component
useInForm?: boolean,
buttonClassName?: string,
size?: 'sm' | 'md' | 'lg',
}
/**
* Component to select an account - supports fuzzy search
* @param root_type - The root type of the account
* @param report_type - The report type of the account
* @param account_type - The type of the account
* @param value - The value of the account field
* @param onChange - The function to call when the value changes
* @returns
*/
const AccountsDropdown = ({ root_type, report_type, account_type, value, onChange, readOnly, disabled, company, filterFunction, useInForm, buttonClassName, size = 'md' }: AccountsDropdownProps) => {
const { data } = useGetAccounts(root_type, report_type, account_type, company, filterFunction)
const groupedAccounts = useMemo(() => {
if (!data) return []
const grouped: Record<string, Account[]> = data.reduce((acc, account) => {
const parentAccount = account.parent_account
if (!parentAccount) return acc
if (!acc[parentAccount]) {
acc[parentAccount] = []
}
acc[parentAccount].push(account)
return acc
}, {} as Record<string, Account[]>)
return Object.entries(grouped).map(([parentAccount, accounts]) => ({
// Remove the last abbreviation from the parent account name like "Assets - TCC" should be "Assets", and "Assets - USD - TCC" should be "Assets - USD"
parentAccount: parentAccount.split(" - ").slice(0, -1).join(" - "),
accounts
}))
}, [data])
const searchIndex = useMemo(() => {
if (!data) {
return null
}
return new Fuse(data, {
keys: ['name'],
threshold: 0.5,
includeScore: true
})
}, [data])
const [search, setSearch] = useState("")
const recommendedAccounts = useMemo(() => {
if (!searchIndex || !search) {
return []
}
return searchIndex.search(search).map((result) => result.item)
}, [searchIndex, search])
const [open, setOpen] = useState(false)
const onOpenChange = (open: boolean) => {
if (readOnly) return
setOpen(open)
// setSearch("")
}
const onSelect = (value: string) => {
onChange?.(value)
setOpen(false)
setSearch(value)
}
const buttonRef = useRef<HTMLButtonElement>(null)
const [width, setWidth] = useState(320)
useLayoutEffect(() => {
if (buttonRef.current) {
setWidth(buttonRef.current.getBoundingClientRect().width)
}
}, [])
return (
<Popover open={open} onOpenChange={onOpenChange} modal={true}>
<PopoverTrigger asChild>
{useInForm ? <FormControl>
<Button
variant="subtle"
type='button'
size={size}
role="combobox"
ref={buttonRef}
tabIndex={0}
disabled={disabled || readOnly}
aria-readonly={readOnly}
aria-expanded={open}
className={cn("w-full justify-between font-normal",
readOnly ? "bg-surface-gray-1 pointer-events-none" : ""
, buttonClassName)}>
{value || _('Select Account')}
<ChevronDownIcon className="ms-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
: <Button
variant="subtle"
size={size}
type='button'
role="combobox"
ref={buttonRef}
disabled={disabled}
aria-expanded={open}
className={cn("w-full justify-between font-normal",
readOnly ? "bg-surface-gray-1" : ""
)}>
{value || _('Select Account')}
<ChevronDownIcon className="ms-2 h-4 w-4 shrink-0 opacity-50" />
</Button>}
</PopoverTrigger>
<PopoverContent className="p-0" style={{ minWidth: width }} align="start">
<Command shouldFilter={false} className="w-full">
<CommandInput placeholder={_("Search account...")} onValueChange={setSearch} value={search} />
<CommandList>
<CommandEmpty>{_("No accounts found.")}</CommandEmpty>
{recommendedAccounts.length > 0 && (
<CommandGroup heading={_("Search Results")}>
{recommendedAccounts.map((account) => (
<CommandItem key={account.name} onSelect={() => onSelect(account.name)}>{account.name}</CommandItem>
))}
</CommandGroup>
)}
{!search && groupedAccounts.map((group) => (
<CommandGroup key={group.parentAccount} heading={group.parentAccount}>
{group.accounts.map((account) => (
<CommandItem key={account.name} onSelect={() => onSelect(account.name)}>{account.name}</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
interface Account {
name: string
root_type: 'Asset' | 'Liability' | 'Equity' | 'Income' | 'Expense'
report_type: 'Balance Sheet' | 'Profit and Loss'
account_type: string
account_currency: string
parent_account: string
}
export const useGetAccounts = (root_type?: ('Asset' | 'Liability' | 'Equity' | 'Income' | 'Expense')[], report_type?: 'Balance Sheet' | 'Profit and Loss', account_type?: string[], company?: string,
filterFunction?: (account: Account) => boolean) => {
const currentCompany = useCurrentCompany()
const { data, isLoading, error, mutate } = useFrappeGetDocList<Account>("Account", {
fields: ["name", "root_type", "report_type", "account_type", "account_currency", "parent_account"],
filters: [["is_group", "=", 0], ["disabled", "=", 0], ["company", "=", company ?? currentCompany]],
limit: 1000,
orderBy: {
"field": "root_type",
// @ts-expect-error - we can pass in additional fields to orderBy
"order": "asc, account_number asc"
}
}, `accounts-${company ?? currentCompany}`, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
})
const filteredData = useMemo(() => {
return data?.filter((account) => {
if (root_type && !root_type.includes(account.root_type)) return false
if (report_type && account.report_type !== report_type) return false
if (account_type && !account_type.includes(account.account_type)) return false
if (filterFunction) return filterFunction(account)
return true
}) ?? []
}, [data, root_type, report_type, account_type, filterFunction])
return { data: filteredData, isLoading, error, mutate }
}
export default AccountsDropdown

View File

@@ -0,0 +1,26 @@
import { cn } from '@/lib/utils'
import { SelectedBank } from '../features/BankReconciliation/bankRecAtoms'
import { useTheme } from '../ui/theme-provider'
import { Landmark } from 'lucide-react'
import { H4 } from '../ui/typography'
const BankLogo = ({ bank, className, imageClassName, iconSize = '18px', iconClassName }: { bank?: SelectedBank | null, className?: string, imageClassName?: string, iconSize?: string, iconClassName?: string }) => {
const { themeValue } = useTheme()
return (
<div className={cn('h-6 flex items-center gap-1', className)}> {bank?.logo ? <img
src={`/assets/erpnext/images/bank-logos/${themeValue === 'Dark' ? (bank.logoDark ?? bank.logo) : bank.logo}`}
alt={bank.bank || bank.name || ''}
className={cn("h-6 max-w-22 me-auto object-contain", imageClassName, {
'dark:invert dark:brightness-0': bank.darkModeInvert
}, bank.logoClassName)}
/> : <>
<Landmark size={iconSize} className={iconClassName} />
<H4 className={cn("text-xs -mb-0.5", {
})}>{bank?.bank ?? ''}</H4>
</>
}</div>
)
}
export default BankLogo

View File

@@ -0,0 +1,17 @@
import { CheckCircle } from 'lucide-react'
import { Progress } from '../ui/progress'
import _ from '@/lib/translate'
const FileUploadBanner = ({
uploadProgress,
}: { uploadProgress: number }) => {
return <div className="flex items-center justify-center flex-col gap-4">
<div className="flex flex-col items-center gap-4">
<CheckCircle size={48} className="text-ink-green-3" />
<span className="text-ink-gray-8 text-p-base">{_("The document has been created and reconciled. Uploading attachments...")}</span>
<Progress value={Math.round(uploadProgress * 100)} size="lg" />
</div>
</div>
}
export default FileUploadBanner

View File

@@ -0,0 +1,301 @@
import { useDocType } from "@/hooks/useDocType";
import { getSystemDefault, slug } from "@/lib/frappe";
import { Filter, useFrappeGetCall } from "frappe-react-sdk"
import { useLayoutEffect, useMemo, useRef, useState } from "react";
import { canCreateDocument } from "@/lib/permissions";
import { useDebounceValue } from "usehooks-ts";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { FormControl } from "../ui/form";
import { ChevronDownIcon, ExternalLink } from "lucide-react";
import { Button } from "../ui/button";
import { cn } from "@/lib/utils";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "../ui/command";
import _ from "@/lib/translate";
import ErrorBanner from "../ui/error-banner";
import MarkdownRenderer from "../ui/markdown";
export interface ResultItem {
value: string,
description: string,
label?: string
}
export interface LinkFieldComboboxProps {
/** DocType to be fetched */
doctype: string;
/** Filters to be applied. Default: none */
filters?: Filter[]
/** Number of records to paginate with. Default: Comes from System Settings or 10 */
limit?: number;
/**
* API to call to fetch records.
*
* Default: `frappe.desk.search.search_link`
*
* If you want to use a custom API, you can pass the path to the API here.
*
* The API should return a list of documents in the following format:
* [{value: string, description: string, label?: string}] - where the value is the ID of the document.
*
* If the API sends a label, it will be used as the label in the dropdown.
*/
searchAPIPath?: string;
/**
* Field you want to search against in the doctype.
*
* Default: `name`
*
* If you want to search against a different field, you can pass the fieldname here.
*
* If you want to search against multiple fields, you can try using the `searchAPIPath` prop to call a custom API,
* or use a custom query in the `customQuery` prop.
*/
searchfield?: string;
/**
* Custom query to be used to fetch records.
*
* If you want to use a custom query, you can pass the query here.
*
* The query should be in the following format:
* {
* query: string,
* filters: {
* fieldname: string,
* operator: string,
* value: string
* }
* }
*/
customQuery?: {
/** Path to function for the query.
*
* Refer: Item/Supplier query
*/
query: string,
/** Filters are usually an object instead of an array in a custom query */
filters?: Record<string, string | number | boolean>,
},
/**
* Used for certain queries where a reference doctype is needed.
*
* For example when searching a supplier in a "Purchase Invoice", the reference_doctype is "Purchase Invoice"
*/
reference_doctype?: string,
/** Placeholder for the dropdown. Default: `doctype` */
placeholder?: string;
/**
* Should the field be read-only.
*/
readOnly?: boolean;
/** Should the field be disabled. Default: false */
disabled?: boolean;
/**
* Function to filter the options based on the input value/other criteria.
*
* For example, you might want to limit the companies shown in the dropdown since they have been already added (like in Cost Codes)
*/
filterFn?: (option: ResultItem, inputValue: string) => boolean,
value?: string,
onChange: (value: string) => void,
/** If true, the component will be wrapped in a FormControl component */
useInForm?: boolean,
/** Button Class name */
buttonClassName?: string,
size?: 'sm' | 'md' | 'lg',
}
const LinkFieldCombobox = ({
doctype,
reference_doctype,
filters = [],
value,
onChange,
readOnly,
disabled,
filterFn,
placeholder = `Select ${doctype}`,
customQuery,
searchfield,
searchAPIPath = "frappe.desk.search.search_link",
limit,
useInForm,
buttonClassName,
size = 'md'
}: LinkFieldComboboxProps) => {
const pageLimit = useMemo(() => limit || getSystemDefault('link_field_results_limit') || 20, [limit])
/** Load the Doctype meta so that we can determine the search fields + the name of the title field */
const { data: meta } = useDocType(doctype)
const userCanCreate = useMemo(() => canCreateDocument(doctype), [doctype])
const [open, setOpen] = useState(false)
const [searchInput, setSearchInput] = useDebounceValue('', 400)
const { data: linkTitleData } = useFrappeGetCall('frappe.client.get_value', {
doctype,
filters: JSON.stringify({
name: value
}),
fieldname: meta?.title_field
}, (meta?.show_title_field_in_link ?? false) && (meta?.title_field) && value ? `link_title::${doctype}::${value}` : null, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
})
const linkTitle = meta?.title_field && meta?.show_title_field_in_link ? (linkTitleData?.message?.[meta?.title_field] ?? value) : value
const buttonRef = useRef<HTMLButtonElement>(null)
const [width, setWidth] = useState(320)
useLayoutEffect(() => {
if (buttonRef.current) {
setWidth(buttonRef.current.getBoundingClientRect().width)
}
}, [])
const { data, error, isLoading } = useFrappeGetCall<{ message: ResultItem[] }>(searchAPIPath, {
doctype,
txt: searchInput,
page_length: pageLimit,
query: customQuery?.query,
searchfield,
filters: JSON.stringify(customQuery?.filters || filters || []),
reference_doctype,
}, () => {
if (!open) {
return null
} else {
let key = `${searchAPIPath}_${doctype}_${searchInput}`
if (pageLimit) {
key += `_${pageLimit}`
}
if (customQuery?.filters) {
key += `_${JSON.stringify(customQuery.filters)}`
} else if (filters) {
key += `_${JSON.stringify(filters)}`
}
if (customQuery && customQuery.query) {
key += `_${customQuery.query}`
}
if (reference_doctype) {
key += `_${reference_doctype}`
}
if (searchfield && searchfield !== 'name') {
key += `_${searchfield}`
}
return key
}
}, {
revalidateOnFocus: false,
revalidateIfStale: false,
shouldRetryOnError: false,
revalidateOnReconnect: false,
})
const onOpenChange = (open: boolean) => {
if (readOnly) return
setOpen(open)
setSearchInput("")
}
const onSelect = (value: string) => {
onChange?.(value)
setOpen(false)
}
const items = filterFn ? data?.message?.slice(0, 50).filter((item) => filterFn(item, searchInput)) : data?.message
const buttonProps = {
variant: "subtle",
type: 'button',
size: size,
role: "combobox",
"data-state": open ? "open" : "closed",
ref: buttonRef,
tabIndex: 0,
disabled: disabled || readOnly,
"aria-expanded": open,
"aria-readonly": readOnly,
className: cn("w-full justify-between font-normal group border border-transparent outline-none",
"data-[state=open]:bg-surface-white data-[state=open]:border-outline-gray-4 data-[state=open]:shadow-sm",
readOnly ? "bg-surface-gray-1" : "",
// Placeholder and value styling
linkTitle ? "text-ink-gray-7" : "text-ink-gray-4",
buttonClassName)
} as const
return (
<Popover open={open} onOpenChange={onOpenChange} modal={true}>
<PopoverTrigger asChild>
{useInForm ? <FormControl>
<Button {...buttonProps}>
{linkTitle || placeholder}
<div className="flex items-center gap-1">
{value && <a href={`/desk/${slug(doctype)}/${value}`} target="_blank" className="group-hover:block hidden">
<ExternalLink className="size-4 shrink-0 opacity-50" />
</a>}
<ChevronDownIcon className="ms-2 size-4 shrink-0" />
</div>
</Button>
</FormControl>
: <Button {...buttonProps}>
{linkTitle || placeholder}
<div className="flex items-center gap-1">
{value && <a href={`/desk/${slug(doctype)}/${value}`} target="_blank" className="group-hover:block hidden">
<ExternalLink className="size-4 shrink-0 opacity-50" />
</a>}
<ChevronDownIcon className="ms-2 size-4 shrink-0" />
</div>
</Button>}
</PopoverTrigger>
<PopoverContent className="p-0" style={{ minWidth: width }} align="start">
{error && <ErrorBanner error={error} />}
<Command shouldFilter={false} className="w-full">
<CommandInput placeholder={placeholder} onValueChange={setSearchInput} />
<CommandList>
<CommandEmpty>{isLoading ? _("Loading...") : _("No results found.")}</CommandEmpty>
<CommandGroup>
{items?.map((result) => (
<CommandItem key={result.value} onSelect={() => onSelect(result.value)} className="flex flex-col items-start gap-0.5">
<span className="font-medium">
{result.label || result.value}
</span>
{result.description && <span className="text-xs text-ink-gray-5">
<MarkdownRenderer content={result.description} />
</span>}
</CommandItem>
))}
{userCanCreate && <CommandItem asChild>
<a href={`/desk/${slug(doctype)}/new-${slug(doctype)}-1`}
target="_blank"
className="hover:underline underline-offset-4 cursor-pointer flex justify-between items-center">
{_("Create New {0}", [doctype])}
<ExternalLink />
</a>
</CommandItem>}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
export default LinkFieldCombobox

View File

@@ -0,0 +1,82 @@
import { Select, SelectValue, SelectTrigger, SelectContent, SelectItem } from '@/components/ui/select'
import _ from '@/lib/translate'
import { useFrappeGetDocList } from 'frappe-react-sdk'
import { ComponentProps, useMemo } from 'react'
import { FormControl } from '../ui/form'
export type PartyTypeDropdownProps = {
value?: string,
onChange?: (value: string) => void,
readOnly?: boolean,
disabled?: boolean,
/** Set this to order the parties so that suggested types are shown first */
type?: 'Receivable' | 'Payable'
/** Set this to true if you want to hide other options by type. e.g. - if type is Receivable, Payable options like "Supplier" will be hidden */
hideOptionsByType?: boolean,
valueProps?: ComponentProps<typeof SelectValue>,
triggerProps?: ComponentProps<typeof SelectTrigger>,
// If true, the component will be wrapped in a FormControl component
useInForm?: boolean
}
const PartyTypeDropdown = ({ value, onChange, readOnly, disabled, type, hideOptionsByType, valueProps, triggerProps, useInForm }: PartyTypeDropdownProps) => {
const { data } = useFrappeGetDocList("Party Type", {
fields: ['name', 'account_type'],
orderBy: {
field: 'creation',
order: 'asc'
}
}, `party_types`, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
})
const filteredData = useMemo(() => {
let options = data ?? [
{ name: "Customer", account_type: "Receivable" },
{ name: "Supplier", account_type: "Payable" },
{ name: "Employee", account_type: "Payable" },
{ name: "Shareholder", account_type: "Payable" },
]
if (hideOptionsByType && type) {
options = options.filter((option) => option.account_type === type)
}
// Order by type if type is set
if (type) {
options = options.sort((a) => a.account_type === type ? -1 : 1)
}
return options
}, [data, type, hideOptionsByType])
const onSelect = (value: string) => {
if (!readOnly) {
onChange?.(value)
}
}
return (
<Select onValueChange={onSelect} value={value} disabled={disabled}>
{useInForm ? <FormControl>
<SelectTrigger tabIndex={0} aria-readonly={readOnly} disabled={disabled || readOnly} {...triggerProps}>
<SelectValue placeholder={_("Type")} aria-readonly={readOnly} {...valueProps} />
</SelectTrigger>
</FormControl> : <SelectTrigger tabIndex={0} {...triggerProps}>
<SelectValue placeholder={_("Type")} aria-readonly={readOnly} {...valueProps} />
</SelectTrigger>
}
<SelectContent>
{filteredData.map((option) => (
<SelectItem key={option.name} value={option.name}>{option.name}</SelectItem>
))}
</SelectContent>
</Select>
)
}
export default PartyTypeDropdown

View File

@@ -0,0 +1,475 @@
import { Button } from '@/components/ui/button'
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import _ from '@/lib/translate'
import { useAtomValue, useSetAtom } from 'jotai'
import { ArrowDownRight, ArrowRightLeftIcon, ArrowUpRight, CalendarIcon, CircleXIcon, GitCompareIcon, HistoryIcon, LandmarkIcon, Loader2Icon, ReceiptIcon, ReceiptTextIcon, UserIcon, WalletIcon } from 'lucide-react'
import { useMemo, useState } from 'react'
import { ActionLogItem, ActionLog as ActionLogType, bankRecActionLog, bankRecDateAtom, bankRecMatchFilters, SelectedBank, selectedBankAccountAtom } from '../BankReconciliation/bankRecAtoms'
import { useHotkeys } from 'react-hotkeys-hook'
import { useGetBankAccounts } from '../BankReconciliation/utils'
import { getCompanyCurrency } from '@/lib/company'
import { formatCurrency } from '@/lib/numbers'
import dayjs from 'dayjs'
import { cn } from '@/lib/utils'
import { formatDate } from '@/lib/date'
import { Separator } from '@/components/ui/separator'
import { slug } from '@/lib/frappe'
import { PaymentEntry } from '@/types/Accounts/PaymentEntry'
import { JournalEntry } from '@/types/Accounts/JournalEntry'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { Table, TableCell, TableBody, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'
import { useFrappePostCall, useSWRConfig } from 'frappe-react-sdk'
import { toast } from 'sonner'
import { getErrorMessage } from '@/lib/frappe'
import ErrorBanner from '@/components/ui/error-banner'
import SelectedTransactionDetails from '../BankReconciliation/SelectedTransactionDetails'
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'
import BankLogo from '@/components/common/BankLogo'
const ActionLog = () => {
const [isOpen, setIsOpen] = useState(false)
useHotkeys('meta+z', () => {
setIsOpen(true)
}, {
enabled: true,
enableOnFormTags: false,
preventDefault: true
})
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<Tooltip>
<TooltipTrigger asChild>
<DialogTrigger asChild>
<Button variant={'outline'} isIconButton size='md'>
<HistoryIcon />
</Button>
</DialogTrigger>
</TooltipTrigger>
<TooltipContent>
{_("Reconciliation History")}
</TooltipContent>
</Tooltip>
<DialogContent className='min-w-[90vw]'>
<DialogHeader>
<DialogTitle>{_("Reconciliation History")}</DialogTitle>
<DialogDescription>{_("View all reconciliation actions taken in this session.")}</DialogDescription>
</DialogHeader>
<ActionLogDialogContent />
<DialogFooter>
<DialogClose asChild>
<Button variant={'outline'} size='md' onClick={() => setIsOpen(false)}>{_("Close")}</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
const ActionLogDialogContent = () => {
const actionLog = useAtomValue(bankRecActionLog)
return <div className='flex flex-col gap-2'>
{actionLog.map((action) => (
<div key={action.timestamp} className='flex flex-col gap-1'>
<ActionGroupHeader action={action} />
<div>
<div className='ms-2 border-s border-s-outline-gray-2 py-1'>
<div className='ms-5'>
{action.items.map((item, index) => (
<Row
item={item}
key={item.bankTransaction.name}
index={index}
action={action}
isLast={index === action.items.length - 1} />
))}
</div>
</div>
</div>
</div>
))}
{actionLog.length === 0 && <Empty>
<EmptyMedia>
<HistoryIcon />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{_("No reconciliation actions found")}</EmptyTitle>
<EmptyDescription>{_("You have not performed any reconciliations in this session yet.")}</EmptyDescription>
</EmptyHeader>
</Empty>}
</div>
}
const ActionGroupHeader = ({ action }: { action: ActionLogType }) => {
const label = useMemo(() => {
switch (action.type) {
case 'match':
return _("Matched")
case 'payment':
if (action.isBulk) {
return _("Bulk Payment")
}
return _("Payment")
case 'transfer':
if (action.isBulk) {
return _("Bulk Transfer")
}
return _("Transfer")
case 'bank_entry':
if (action.isBulk) {
return _("Bulk Bank Entry")
}
return _("Bank Entry")
default:
return _("Action")
}
}, [action])
return <div className='flex items-center gap-2 text-ink-gray-5'>
{action.type === 'match' && <GitCompareIcon className='w-4 h-4' />}
{action.type === 'payment' && <ReceiptIcon className='w-4 h-4' />}
{action.type === 'transfer' && <ArrowRightLeftIcon className='w-4 h-4' />}
{action.type === 'bank_entry' && <LandmarkIcon className='w-4 h-4' />}
<span className='flex items-center gap-2 text-sm'>
{label} - {dayjs(action.timestamp).fromNow()}
</span>
</div>
}
const Row = ({ item, index, isLast, action }: { item: ActionLogItem, index: number, isLast: boolean, action: ActionLogType }) => {
const isWithdrawal = item.bankTransaction.withdrawal && item.bankTransaction.withdrawal > 0
const { banks } = useGetBankAccounts()
const bank = useMemo(() => {
if (item.bankTransaction.bank_account) {
return banks?.find((bank) => bank.name === item.bankTransaction.bank_account)
}
return null
}, [item.bankTransaction.bank_account, banks])
const amount = item.bankTransaction.withdrawal ? item.bankTransaction.withdrawal : item.bankTransaction.deposit
const currency = item.bankTransaction.currency || getCompanyCurrency(item.bankTransaction.company ?? '')
return <div className='flex items-center gap-2 group'>
<div className={cn('p-3.5 group-hover:bg-surface-gray-1 border-s border-e border-t w-full', isLast ? 'rounded-b border-b' : '', index === 0 ? 'rounded-t' : '')}>
<div className='flex justify-between items-center'>
<div className='flex flex-col gap-2'>
<p className='text-p-base'>{item.bankTransaction.description}</p>
<div className='flex items-center gap-3'>
<div className='flex gap-2 items-center'>
<BankLogo bank={bank} className='h-4 mb-0' iconSize='16px' />
<span className='text-sm text-ink-gray-5'>{item.bankTransaction.bank_account}</span>
</div>
<Separator orientation='vertical' />
<div className='flex items-center gap-2 text-ink-gray-5 text-sm' title={_("Transaction Date")}>
<CalendarIcon className='w-4 h-4' />
<span className='text-sm'>{formatDate(item.bankTransaction.date, 'Do MMM YYYY')}</span>
</div>
<Separator orientation='vertical' />
<div>
<div className='flex items-center gap-1' title={isWithdrawal ? _("Spent") : _("Received")}>
{isWithdrawal ? <ArrowUpRight className="w-5 h-5 text-ink-red-3" /> : <ArrowDownRight className="w-5 h-5 text-ink-green-3" />}
<span className='text-sm text-ink-gray-5'>{formatCurrency(amount, currency)}</span>
</div>
</div>
</div>
</div>
<div className='flex justify-end items-center gap-2'>
<div className='text-end flex flex-col gap-2'>
<a
href={`/desk/${slug(item.voucher.reference_doctype)}/${item.voucher.reference_name}`}
target='_blank'
className='underline underline-offset-4 text-base'>
{["Payment Entry", "Journal Entry"].includes(item.voucher.reference_doctype) ? "" : _("{} :", [item.voucher.reference_doctype])} {item.voucher.reference_name}
</a>
{item.voucher.reference_doctype === "Payment Entry" && item.voucher.doc && <PaymentEntryDetails item={item} />}
{item.voucher.reference_doctype === "Journal Entry" && <JournalEntryDetails item={item} bank={bank} />}
</div>
</div>
</div>
</div>
<div className='w-10 h-10 flex items-center justify-center'>
<CancelActionLogItem item={item} type={action.type} timestamp={action.timestamp} bank={bank} />
</div>
</div>
}
const JournalEntryDetails = ({ item, bank }: { item: ActionLogItem, bank?: SelectedBank | null }) => {
return <div className='flex items-center gap-2 text-ink-gray-5 justify-end'>
<WalletIcon className='w-4 h-4' />
<JournalEntryAccountsTable item={item} bank={bank} />
</div>
}
const JournalEntryAccountsTable = ({ item, bank }: { item: ActionLogItem, bank?: SelectedBank | null }) => {
const accounts = useMemo(() => {
const allAccounts = (item.voucher.doc as JournalEntry).accounts
return allAccounts.filter((acc) => bank ? acc.account !== bank.account : true)
}, [item, bank])
return <>
{accounts.length === 1 ? <span className='text-sm'>{accounts[0].account}</span> :
<HoverCard>
<HoverCardTrigger>
<span className='text-sm cursor-pointer hover:underline underline-offset-4'>{_("Split across {} accounts", [accounts.length.toString()])}</span>
</HoverCardTrigger>
<HoverCardContent className='w-full p-2' align='end'>
<Table>
<TableHeader>
<TableRow>
<TableHead>{_("Account")}</TableHead>
<TableHead className='text-end'>{_("Debit")}</TableHead>
<TableHead className='text-end'>{_("Credit")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{accounts.map((account) => (
<TableRow key={account.account}>
<TableCell>{account.account}</TableCell>
<TableCell className='text-end font-numeric'>{formatCurrency(account.debit ?? 0, account.account_currency ?? '')}</TableCell>
<TableCell className='text-end font-numeric'>{formatCurrency(account.credit ?? 0, account.account_currency ?? '')}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</HoverCardContent>
</HoverCard>
}</>
}
const PaymentEntryDetails = ({ item, className }: { item: ActionLogItem, className?: string }) => {
if ((item.voucher.doc as PaymentEntry).payment_type === "Internal Transfer") {
return <TransferDetails item={item} className={className} />
}
const invoices = (item.voucher.doc as PaymentEntry).references ?? []
const currency = item.bankTransaction.withdrawal && item.bankTransaction.withdrawal > 0 ? (item.voucher.doc as PaymentEntry)?.paid_to_account_currency : (item.voucher.doc as PaymentEntry)?.paid_from_account_currency
return <div className='flex items-center gap-3'>
<div className={cn('flex items-center gap-2 text-ink-gray-5 text-sm', className)}>
<UserIcon className='w-4 h-4' />
<span className='text-sm'>{(item.voucher.doc as PaymentEntry).party_name}</span>
</div>
<Separator orientation='vertical' />
<HoverCard>
<HoverCardTrigger>
<div className={cn('flex items-center gap-2 text-ink-gray-5 text-sm', className)}>
<ReceiptTextIcon className='w-4 h-4' />
<span className='text-sm cursor-pointer hover:underline underline-offset-4'>{invoices.length === 0 ? _("No invoice linked") : invoices.length === 1 ? _("1 invoice") : _("{} invoices", [invoices.length.toString()])}</span>
</div>
</HoverCardTrigger>
<HoverCardContent className='w-full p-2' align='end'>
<div className='flex flex-col gap-2'>
{invoices.map((invoice) => (
<Table>
<TableHeader>
<TableRow>
<TableHead>{_("Document")}</TableHead>
<TableHead>{_("Invoice No")}</TableHead>
<TableHead>{_("Due Date")}</TableHead>
<TableHead className='text-end'>{_("Grand Total")}</TableHead>
<TableHead className='text-end'>{_("Allocated")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell><a href={`/desk/${slug(invoice.reference_doctype)}/${invoice.reference_name}`} target='_blank' className='underline underline-offset-4'>{invoice.reference_doctype}: {invoice.reference_name}</a></TableCell>
<TableCell>{invoice.bill_no ?? "-"}</TableCell>
<TableCell>{formatDate(invoice.due_date)}</TableCell>
<TableCell className='text-end font-numeric'>{formatCurrency(invoice.total_amount, currency ?? '')}</TableCell>
<TableCell className='text-end font-numeric'>{formatCurrency(invoice.allocated_amount, currency ?? '')}</TableCell>
</TableRow>
</TableBody>
</Table>
))}
</div>
</HoverCardContent>
</HoverCard>
</div>
}
const TransferDetails = ({ item, className }: { item: ActionLogItem, className?: string }) => {
const { banks } = useGetBankAccounts()
const bank = useMemo(() => {
const isWithdrawal = item.bankTransaction.withdrawal && item.bankTransaction.withdrawal > 0
let transferAccount = ""
if (isWithdrawal) {
transferAccount = (item.voucher.doc as PaymentEntry).paid_to
} else {
transferAccount = (item.voucher.doc as PaymentEntry).paid_from
}
const transferBankAccount = banks?.find((bank) => bank.account === transferAccount)
return transferBankAccount
}, [banks, item])
return <div className={cn('flex items-center gap-2 text-ink-gray-5 text-sm', className)}>
<BankLogo bank={bank} className='h-5 mb-0' iconSize='16px' imageClassName='max-h-5' />
<span className='text-sm'>{bank?.account}</span>
</div>
}
const ACTION_TYPE_MAP = {
'bank_entry': _("Bank Entry"),
'payment': _("Payment"),
'transfer': _("Transfer"),
'match': _("Match"),
}
const CancelActionLogItem = ({ item, type, timestamp, bank }: { item: ActionLogItem, type: ActionLogType['type'], timestamp: number, bank?: SelectedBank | null }) => {
const [isOpen, setIsOpen] = useState(false)
const { call, loading, error } = useFrappePostCall('erpnext.accounts.doctype.bank_transaction.bank_transaction.unreconcile_transaction_entry')
const { mutate } = useSWRConfig()
const actionLog = useSetAtom(bankRecActionLog)
const dates = useAtomValue(bankRecDateAtom)
const matchFilters = useAtomValue(bankRecMatchFilters)
const selectedBank = useAtomValue(selectedBankAccountAtom)
const onUndo = () => {
call({
bank_transaction_id: item.bankTransaction.name,
voucher_type: item.voucher.reference_doctype,
voucher_id: item.voucher.reference_name,
}).then(() => {
toast.success(type === 'match' ? _("Unmatched") : _("Cancelled"))
if (selectedBank?.name === item.bankTransaction.bank_account) {
mutate(`bank-reconciliation-unreconciled-transactions-${selectedBank?.name}-${dates.fromDate}-${dates.toDate}`)
mutate(`bank-reconciliation-account-closing-balance-${selectedBank?.name}-${dates.toDate}`)
// Update the matching vouchers for the selected transaction
mutate(`bank-reconciliation-vouchers-${item.bankTransaction.name}-${dates.fromDate}-${dates.toDate}-${matchFilters.join(',')}`)
}
setTimeout(() => {
actionLog((prev) => {
// Find the action and then remove the item from the action. If the action is empty, remove the action from the array
const action = prev.find((action) => action.timestamp === timestamp)
if (action) {
action.items = action.items.filter((i) => i.bankTransaction.name !== item.bankTransaction.name)
}
// If the action is empty, remove the action from the array
if (action && action.items.length === 0) {
return prev.filter((a) => a.timestamp !== timestamp)
} else {
return prev.map((a) => a.timestamp === timestamp ? { ...a, items: action?.items ?? [] } : a)
}
})
}, 100)
setIsOpen(false)
}).catch((error) => {
toast.error(_("There was an error while performing the action."), {
duration: 5000,
description: getErrorMessage(error),
})
})
}
return <AlertDialog open={isOpen} onOpenChange={setIsOpen}>
<Tooltip>
<TooltipTrigger asChild>
<AlertDialogTrigger asChild>
<Button
variant={'ghost'}
isIconButton
theme='red'
title={_("Cancel")}
className='hover:text-ink-red-3 hover:bg-destructive/5 text-ink-gray-5 hidden group-hover:inline-flex'>
<CircleXIcon className='w-8 h-8' />
</Button>
</AlertDialogTrigger>
</TooltipTrigger>
<TooltipContent>
{_("Cancel")}
</TooltipContent>
</Tooltip>
<AlertDialogContent className='min-w-3xl'>
<AlertDialogHeader>
<AlertDialogTitle>{type === 'match' ? _("Unmatch Transaction?") : _("Undo {}?", [item.voucher.reference_doctype])}</AlertDialogTitle>
<AlertDialogDescription>{type === 'match' ? _("Are you sure you want to unmatch the voucher from this transaction?") : _("Are you sure you want to cancel this {} {}?", [_(item.voucher.reference_doctype), item.voucher.reference_name])}</AlertDialogDescription>
</AlertDialogHeader>
{error && <ErrorBanner error={error} />}
<div className='flex flex-col gap-2'>
<SelectedTransactionDetails transaction={item.bankTransaction} />
<Table>
<TableRow>
<TableHead>{_("Action Type")}</TableHead>
<TableCell>{ACTION_TYPE_MAP[type]}</TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Voucher Type")}</TableHead>
<TableCell>{_(item.voucher.reference_doctype)}</TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Voucher Name")}</TableHead>
<TableCell><a href={`/desk/${slug(item.voucher.reference_doctype)}/${item.voucher.reference_name}`} target='_blank' className='underline underline-offset-4'>{item.voucher.reference_name}</a></TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Posting Date")}</TableHead>
<TableCell>{formatDate(item.voucher.posting_date, 'Do MMM YYYY')}</TableCell>
</TableRow>
{type === 'transfer' && item.voucher.doc && <TableRow>
<TableHead>{_("Transfer Account")}</TableHead>
<TableCell>
<TransferDetails item={item} className='text-ink-gray-8' />
</TableCell>
</TableRow>}
{type === 'payment' && item.voucher.doc && <TableRow>
<TableHead>{_("Payment Details")}</TableHead>
<TableCell>
<PaymentEntryDetails item={item} className='text-ink-gray-8' />
</TableCell>
</TableRow>}
{type === 'bank_entry' && item.voucher.doc && <TableRow>
<TableHead>{_("Account")}</TableHead>
<TableCell><JournalEntryAccountsTable item={item} bank={bank} /></TableCell>
</TableRow>}
</Table>
</div>
<AlertDialogFooter>
<AlertDialogCancel disabled={loading}>
{_("Close")}
</AlertDialogCancel>
<Button theme="red" size='md' disabled={loading} onClick={onUndo}>
{loading ? <Loader2Icon className='w-4 h-4 animate-spin' /> : _(("Undo"))}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
}
export default ActionLog

View File

@@ -0,0 +1,334 @@
import { useAtomValue, useSetAtom } from "jotai"
import { bankRecClosingBalanceAtom, bankRecDateAtom, SelectedBank, selectedBankAccountAtom } from "./bankRecAtoms"
import { FrappeConfig, FrappeContext, useFrappeGetDocCount, useFrappeGetDocList, useFrappePostCall, useSWRConfig } from "frappe-react-sdk"
import { BankTransaction } from "@/types/Accounts/BankTransaction"
import { Progress } from "@/components/ui/progress"
import { useGetAccountClosingBalance, useGetAccountClosingBalanceAsPerStatement, useGetAccountOpeningBalance, useGetUnreconciledTransactions } from "./utils"
import { flt, formatCurrency } from "@/lib/numbers"
import { Skeleton } from "@/components/ui/skeleton"
import { StatContainer, StatLabel, StatValue } from "@/components/ui/stats"
import { Edit, Info, Trash2 } from "lucide-react"
import { H4, Paragraph } from "@/components/ui/typography"
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"
import { getCompanyCurrency } from "@/lib/company"
import _ from "@/lib/translate"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { formatDate } from "@/lib/date"
import { Form } from "@/components/ui/form"
import { CurrencyFormField } from "@/components/ui/form-elements"
import { useForm } from "react-hook-form"
import { Button } from "@/components/ui/button"
import { useContext, useState } from "react"
import { Separator } from "@/components/ui/separator"
import { BankAccountBalance } from "@/types/Accounts/BankAccountBalance"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { toast } from "sonner"
import ErrorBanner from "@/components/ui/error-banner"
const BankBalance = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
if (!bankAccount) {
return null
}
return (
<div className="flex justify-between">
<div className="w-[80%] flex flex-wrap justify-between gap-2 pe-8 border-e-border border-e">
<OpeningBalance />
<ClosingBalance />
<ClosingBalanceAsPerStatement />
<Difference />
</div>
<ReconcileProgress />
</div>
)
}
const OpeningBalance = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const { data, isLoading } = useGetAccountOpeningBalance()
return <StatContainer className="min-w-48">
<StatLabel>{_("Opening Balance")}</StatLabel>
{isLoading ? <Skeleton className="w-[150px] h-5 rounded-sm" /> : <StatValue className="font-numeric">{formatCurrency(flt(data?.message, 2), bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))}</StatValue>}
</StatContainer>
}
const ClosingBalance = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const { data, isLoading } = useGetAccountClosingBalance()
return (
<StatContainer className="min-w-48">
<div className="flex items-start gap-1">
<StatLabel>
{_("Closing Balance as per system")}
</StatLabel>
<HoverCard openDelay={100}>
<HoverCardTrigger>
<Info className="size-3.5 text-ink-gray-6 -mt-px" />
</HoverCardTrigger>
<HoverCardContent className="w-96" align="start" side="right">
<H4 className="text-base">{_("Closing balance as per system")}</H4>
<Paragraph className="mt-2 text-p-sm">
{_("This is what the system expects the closing balance to be in your bank statement.")}
<br />
{_("It takes into account all the transactions that have been posted and subtracts the transactions that have not cleared yet.")}
<br />
{_("If your bank statement shows a different closing balance, it is because all transactions have not reconciled yet.")}
<br /><br />
For more information, click on the <strong>Bank Reconciliation Statement</strong> tab below.
</Paragraph>
</HoverCardContent>
</HoverCard>
</div>
{isLoading ? <Skeleton className="w-[150px] h-5 rounded-sm" /> : <StatValue className="font-numeric">{formatCurrency(flt(data?.message, 2), bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))}</StatValue>}
</StatContainer>
)
}
const Difference = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const { data, isLoading } = useGetAccountClosingBalance()
const value = useAtomValue(bankRecClosingBalanceAtom(bankAccount?.name ?? ''))
const difference = flt(value.value - (data?.message ?? 0))
const isError = difference !== 0
return <StatContainer className="w-fit text-end sm:min-w-56">
<StatLabel className="text-end">{_("Difference")}</StatLabel>
{isLoading ? <Skeleton className="w-[150px] h-5 self-end rounded-sm" /> : <StatValue className={isError ? 'text-ink-red-3 font-numeric' : 'font-numeric'}>
{formatCurrency(difference,
bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))
}</StatValue>}
</StatContainer>
}
const ReconcileProgress = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
const { data: totalCount } = useFrappeGetDocCount<BankTransaction>('Bank Transaction', [
["bank_account", "=", bankAccount?.name ?? ''],
['docstatus', '=', 1],
['date', '<=', dates?.toDate],
['date', '>=', dates?.fromDate]
], false, undefined, {
revalidateOnFocus: false
})
const { data: unreconciledTransactions, } = useGetUnreconciledTransactions()
const reconciledCount = (totalCount ?? 0) - (unreconciledTransactions?.message?.length ?? 0)
const progress = (totalCount ? reconciledCount / totalCount : 0) * 100
return <div className="w-[18%] flex flex-col gap-1 items-end">
<div className="w-full">
<Progress
value={progress}
max={100}
size="md"
label="Progress"
hint
hintText={`${reconciledCount} / ${totalCount} ${_("reconciled")}`} />
</div>
</div>
}
const ClosingBalanceAsPerStatement = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
const setValue = useSetAtom(bankRecClosingBalanceAtom(bankAccount?.name ?? ''))
const { data, isLoading } = useGetAccountClosingBalanceAsPerStatement({
onSuccess: (data) => {
if (data?.message && data?.message?.balance) {
setValue({
value: data?.message?.balance,
stringValue: data?.message?.balance.toString()
})
}
}
})
const isDateSame = data?.message?.date === dates.toDate
const [isOpen, setIsOpen] = useState(false)
return <StatContainer className="min-w-48">
<StatLabel>{_("Closing Balance as per statement")}</StatLabel>
<div className="flex flex-col gap-2 items-start">
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-4 underline cursor-pointer underline-offset-6" role="button">
{isLoading ? <Skeleton className="w-[150px] h-5 rounded-sm" /> : <StatValue className="font-numeric">{formatCurrency(flt(data?.message?.balance, 2), bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))}</StatValue>}
<Edit className="w-4 h-4" />
</div>
</TooltipTrigger>
<TooltipContent>
{_("Click to set the closing balance as per statement")}
</TooltipContent>
</Tooltip>
</DialogTrigger>
<DialogContent className="min-w-xl">
<ClosingBalanceForm
defaultBalance={data?.message?.balance ?? 0}
date={dates.toDate}
bankAccount={bankAccount}
onClose={() => setIsOpen(false)}
/>
</DialogContent>
</Dialog>
{!isDateSame && data?.message.date && <span className="text-xs font-medium text-ink-red-3">{_("As of {0}", [formatDate(data?.message?.date ?? '', 'Do MMM YYYY')])}</span>}
</div>
</StatContainer>
}
const ClosingBalanceForm = ({ defaultBalance, date, bankAccount, onClose }: { defaultBalance: number, date: string, bankAccount: SelectedBank | null, onClose: VoidFunction }) => {
const { mutate } = useSWRConfig()
const form = useForm<{ balance: number }>({
defaultValues: {
balance: defaultBalance
}
})
const setValue = useSetAtom(bankRecClosingBalanceAtom(bankAccount?.name ?? ''))
const { call, loading, error } = useFrappePostCall("erpnext.accounts.doctype.bank_account.bank_account.set_closing_balance_as_per_statement")
const onSubmit = (data: { balance: number }) => {
if (data.balance) {
call({
bank_account: bankAccount?.name ?? '',
date: date,
balance: data.balance
})
.then(() => {
// Mutate the closing balance as per statement
mutate(`bank-reconciliation-account-closing-balance-as-per-statement-${bankAccount?.name}-${date}`)
setValue({
value: data.balance,
stringValue: data.balance.toString()
})
toast.success(_("Closing balance set."))
onClose()
})
} else {
toast.error(_("Closing balance is required."))
}
}
const currency = bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? '')
return <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<DialogHeader>
<DialogTitle>{_("Set closing balance as per bank statement")}</DialogTitle>
<DialogDescription>
{_("Enter the closing balance you see in your bank statement for {0} as of the {1}", [bankAccount?.account_name ?? bankAccount?.name ?? '', formatDate(date, 'Do MMM YYYY')])}
</DialogDescription>
</DialogHeader>
{error && <ErrorBanner error={error} />}
<div className="py-4">
<CurrencyFormField
name="balance"
label={_("Closing balance on bank statement as of {0}", [formatDate(date, 'Do MMM YYYY')])}
isRequired
currency={currency}
/>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant={'outline'} size='md' disabled={loading}>{_("Cancel")}</Button>
</DialogClose>
<Button type='submit' size='md' disabled={loading}>{_("Save")}</Button>
</DialogFooter>
<ClosingBalancesList bankAccount={bankAccount} date={date} />
</form>
</Form>
}
const ClosingBalancesList = ({ bankAccount, date }: { bankAccount: SelectedBank | null, date: string }) => {
const { data, mutate } = useFrappeGetDocList<BankAccountBalance>("Bank Account Balance", {
filters: [["bank_account", "=", bankAccount?.name ?? ''], ["date", "<=", date]],
orderBy: {
field: "date",
order: "desc"
},
fields: ["date", "balance", "name"],
limit: 10
})
const { db } = useContext(FrappeContext) as FrappeConfig
const onDelete = (name: string) => {
toast.promise(db.deleteDoc("Bank Account Balance", name).then(() => {
mutate()
}), {
loading: _("Deleting closing balance..."),
success: _("Closing balance deleted."),
error: _("Failed to delete closing balance.")
})
}
if (data?.length === 0) {
return null
}
return <div>
<Separator className="my-8" />
<p className="text-sm text-center">{_("Balances as per bank statement before {0}", [formatDate(date, 'Do MMM YYYY')])}</p>
<Table>
<TableHeader>
<TableRow>
<TableHead>{_("Date")}</TableHead>
<TableHead className="text-end">{_("Balance")}</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data?.map((item) => (
<TableRow key={item.name}>
<TableCell>{formatDate(item.date, 'Do MMM YYYY')}</TableCell>
<TableCell className="text-end">{formatCurrency(flt(item.balance, 2), bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))}</TableCell>
<TableCell className="text-end">
<Button
title={_("Delete")}
type='button' isIconButton variant='ghost' onClick={() => onDelete(item.name)}>
<Trash2 />
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
}
export default BankBalance

View File

@@ -0,0 +1,355 @@
import { useAtomValue } from "jotai"
import { MissingFiltersBanner } from "./MissingFiltersBanner"
import { bankRecDateAtom, SelectedBank, selectedBankAccountAtom } from "./bankRecAtoms"
import { useCurrentCompany } from "@/hooks/useCurrentCompany"
import { Paragraph } from "@/components/ui/typography"
import type { ColumnDef } from "@tanstack/react-table"
import { useCallback, useMemo, useState } from "react"
import { useFrappeGetCall, useFrappePostCall, useSWRConfig } from "frappe-react-sdk"
import { QueryReportReturnType } from "@/types/custom/Reports"
import { formatDate } from "@/lib/date"
import { ListView, type ListViewColumnMeta } from "@/components/ui/list-view"
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { formatCurrency } from "@/lib/numbers"
import { getCompanyCurrency } from "@/lib/company"
import { slug } from "@/lib/frappe"
import { CheckCircle2, ReceiptTextIcon, XCircle } from "lucide-react"
import ErrorBanner from "@/components/ui/error-banner"
import { Badge } from "@/components/ui/badge"
import _ from "@/lib/translate"
import { useCopyToClipboard } from "usehooks-ts"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Form } from "@/components/ui/form"
import { useForm } from "react-hook-form"
import { DateField } from "@/components/ui/form-elements"
import { Empty, EmptyMedia, EmptyHeader, EmptyTitle, EmptyDescription } from "@/components/ui/empty"
const BankClearanceSummary = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
if (!bankAccount) {
return <MissingFiltersBanner text={_("Please select a bank account to view the bank clearance summary.")} />
}
if (!dates) {
return <MissingFiltersBanner text={_("Please select dates to view the bank clearance summary.")} />
}
return <BankClearanceSummaryView />
}
interface BankClearanceSummaryEntry {
payment_document_type: string
payment_entry: string
posting_date: string,
cheque_no?: string,
amount: number,
against: string,
clearance_date: string,
}
const BankClearanceSummaryView = () => {
const companyID = useCurrentCompany()
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
const filters = useMemo(() => {
return JSON.stringify({
account: bankAccount?.account,
from_date: dates.fromDate,
to_date: dates.toDate
})
}, [bankAccount, dates])
const { data, error, mutate } = useFrappeGetCall<{ message: QueryReportReturnType<BankClearanceSummaryEntry> }>('frappe.desk.query_report.run', {
report_name: 'Bank Clearance Summary',
filters,
ignore_prepared_report: 1,
are_default_filters: false,
}, `Report-Bank Clearance Summary-${filters}`, { keepPreviousData: true, revalidateOnFocus: false }, 'POST')
const formattedFromDate = formatDate(dates.fromDate)
const formattedToDate = formatDate(dates.toDate)
const [, copyToClipboard] = useCopyToClipboard()
const onCopy = useCallback(
(text: string) => {
copyToClipboard(text).then(() => {
toast.success(_("Copied to clipboard"))
})
},
[copyToClipboard, _],
)
const accountCurrency = useMemo(
() => bankAccount?.account_currency ?? getCompanyCurrency(companyID),
[bankAccount?.account_currency, companyID],
)
const clearanceColumns = useMemo<ColumnDef<BankClearanceSummaryEntry, unknown>[]>(
() => [
{
accessorKey: "payment_document_type",
header: _("Document Type"),
size: 140,
cell: ({ row }) => _(row.original.payment_document_type),
},
{
id: "payment_entry",
header: _("Payment Document"),
size: 160,
meta: {
getTooltipText: (r) => {
const x = r as BankClearanceSummaryEntry
return [x.payment_document_type, x.payment_entry].filter(Boolean).join(" · ") || undefined
},
} satisfies ListViewColumnMeta,
cell: ({ row }) => (
<a
target="_blank"
rel="noreferrer"
className="text-ink-gray-8 block min-w-0 w-full underline underline-offset-4"
href={`/desk/${slug(row.original.payment_document_type)}/${row.original.payment_entry}`}
>
{row.original.payment_entry}
</a>
),
},
{
accessorKey: "posting_date",
header: _("Posting Date"),
size: 118,
meta: { tabularNums: true } satisfies ListViewColumnMeta,
cell: ({ row }) => formatDate(row.original.posting_date),
},
{
accessorKey: "cheque_no",
header: _("Cheque/Reference Number"),
size: 160,
cell: ({ row }) => {
const ref = row.original.cheque_no ?? ""
return (
<Tooltip delayDuration={500}>
<TooltipTrigger asChild>
<button
type="button"
className="text-ink-gray-8 hover:underline min-w-0 w-full cursor-pointer truncate text-start underline-offset-4"
onClick={() => onCopy(ref)}
>
{ref}
</button>
</TooltipTrigger>
<TooltipContent>
{ref}
</TooltipContent>
</Tooltip>
)
},
},
{
accessorKey: "clearance_date",
header: _("Clearance Date"),
size: 118,
meta: { tabularNums: true } satisfies ListViewColumnMeta,
cell: ({ row }) => formatDate(row.original.clearance_date),
},
{
accessorKey: "against",
header: _("Against Account"),
size: 250,
},
{
accessorKey: "amount",
header: _("Amount"),
size: 150,
meta: { align: "right" } satisfies ListViewColumnMeta,
cell: ({ row }) => <span className="font-numeric">{formatCurrency(row.original.amount, accountCurrency)}</span>,
},
{
id: "status",
header: _("Status"),
size: 200,
meta: { truncate: false, truncateTooltip: false } satisfies ListViewColumnMeta,
cell: ({ row }) => {
const r = row.original
return r.clearance_date ? (
<Badge theme="green">
<CheckCircle2 />
{_("Cleared")}
</Badge>
) : (
<div className="flex min-w-0 flex-wrap items-center gap-2">
<Badge theme="red">
<XCircle />
{_("Not Cleared")}
</Badge>
<SetClearanceDateButton
voucher={r}
bankAccount={bankAccount}
companyID={companyID}
mutate={mutate}
/>
</div>
)
},
},
],
[_, accountCurrency, bankAccount, companyID, mutate, onCopy],
)
return <div className="space-y-4 py-2">
<div>
<Paragraph className="text-sm">
<span dangerouslySetInnerHTML={{
__html: _("Below is a list of all accounting entries posted against the bank account {0} between {1} and {2}.", [`<strong>${bankAccount?.account}</strong>`, `<strong>${formattedFromDate}</strong>`, `<strong>${formattedToDate}</strong>`])
}} />
</Paragraph>
</div>
{error && <ErrorBanner error={error} />}
{data && data.message.result.length > 0 ? (
<ListView
data={data.message.result}
columns={clearanceColumns}
getRowId={(row) => `${row.payment_entry}-${row.posting_date}`}
maxHeight="calc(100vh - 200px)"
scrollAreaClassName="min-h-[calc(100vh-200px)]"
emptyState={_("No rows to display.")}
/>
) : null}
{data && data.message.result.length == 0 &&
<Empty>
<EmptyMedia>
<ReceiptTextIcon />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{_("No entries found")}</EmptyTitle>
<EmptyDescription>{_("There are no accounting entries in the system for the selected account and dates.")}</EmptyDescription>
</EmptyHeader>
</Empty>
}
</div>
}
const SetClearanceDateButton = ({ voucher, bankAccount, companyID, mutate }: { voucher: BankClearanceSummaryEntry, bankAccount: SelectedBank | null, companyID: string, mutate: VoidFunction }) => {
const [open, setOpen] = useState(false)
const onClose = () => {
setOpen(false)
mutate()
}
return <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger disabled={!bankAccount}>
<Tooltip delayDuration={500}>
<TooltipTrigger>
<Button variant='link' size="sm" className="px-0" theme="red">{_("Force Clear")}</Button>
</TooltipTrigger>
<TooltipContent align='start'>
{_("Set the clearance date for this voucher without reconciling with a bank transaction.")}
</TooltipContent>
</Tooltip>
</DialogTrigger>
<DialogContent className="min-w-2xl">
{bankAccount && <ForceClearVoucherForm voucher={voucher} bankAccount={bankAccount} companyID={companyID} onClose={onClose} />}
</DialogContent>
</Dialog>
}
const ForceClearVoucherForm = ({ voucher, bankAccount, companyID, onClose }: { voucher: BankClearanceSummaryEntry, bankAccount: SelectedBank, companyID: string, onClose: () => void }) => {
const { mutate } = useSWRConfig()
const dates = useAtomValue(bankRecDateAtom)
const form = useForm<{ clearance_date: string }>({
defaultValues: {
clearance_date: voucher.posting_date,
}
})
const { call, loading, error } = useFrappePostCall('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.update_clearance_date')
const onSubmit = (data: { clearance_date: string }) => {
call({
payment_document: voucher.payment_document_type,
payment_entry: voucher.payment_entry,
account: bankAccount.account,
clearance_date: data.clearance_date,
})
.then(() => {
toast.success(_("Clearance date updated"))
onClose()
mutate(`bank-reconciliation-account-closing-balance-${bankAccount?.name}-${dates.toDate}`)
})
}
return <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className='flex flex-col gap-4'>
<DialogHeader>
<DialogTitle>{_("Force Clear Voucher")}</DialogTitle>
<DialogDescription>
{_("Set the clearance date for this voucher without reconciling with a bank transaction.")}
</DialogDescription>
</DialogHeader>
{error && <ErrorBanner error={error} />}
<div>
<Table>
<TableHeader>
<TableRow>
<TableHead>{_("Payment Document")}</TableHead>
<TableCell><a target="_blank" className="underline underline-offset-4"
href={`/desk/${slug(voucher.payment_document_type)}/${voucher.payment_entry}`}>{_(voucher.payment_document_type)} : {voucher.payment_entry}</a></TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Posting Date")}</TableHead>
<TableCell>{formatDate(voucher.posting_date)}</TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Cheque/Reference Number")}</TableHead>
<TableCell title={voucher.cheque_no}>{voucher.cheque_no?.slice(0, 40)}{voucher.cheque_no?.length && voucher.cheque_no?.length > 40 ? "..." : ""}</TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Amount")}</TableHead>
<TableCell className="text-end">{formatCurrency(voucher.amount, bankAccount?.account_currency ?? getCompanyCurrency(companyID))}</TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Against Account")}</TableHead>
<TableCell><a target="_blank" className="underline underline-offset-4" href={`/desk/account/${voucher.against}`}>{voucher.against}</a></TableCell>
</TableRow>
</TableHeader>
</Table>
</div>
<DateField
name='clearance_date'
label={_("Clearance Date")}
isRequired
inputProps={{ autoFocus: true }}
/>
<DialogFooter>
<DialogClose asChild>
<Button variant={'outline'} disabled={loading} size='md'>{_("Cancel")}</Button>
</DialogClose>
<Button type='submit' disabled={loading} size='md'>{_("Submit")}</Button>
</DialogFooter>
</div>
</form>
</Form>
}
export default BankClearanceSummary

View File

@@ -0,0 +1,831 @@
import { useAtom, useAtomValue, useSetAtom } from "jotai"
import { bankRecRecordJournalEntryModalAtom, bankRecSelectedTransactionAtom, bankRecUnreconcileModalAtom, selectedBankAccountAtom } from "./bankRecAtoms"
import { Dialog, DialogContent, DialogTitle, DialogDescription, DialogHeader, DialogFooter, DialogClose } from "@/components/ui/dialog"
import _ from "@/lib/translate"
import { UnreconciledTransaction, useGetRuleForTransaction, useRefreshUnreconciledTransactions, useUpdateActionLog } from "./utils"
import { useFieldArray, useForm, useFormContext, useWatch } from "react-hook-form"
import { JournalEntry } from "@/types/Accounts/JournalEntry"
import { getCompanyCostCenter, getCompanyCurrency } from "@/lib/company"
import { FrappeConfig, FrappeContext, useFrappePostCall } from "frappe-react-sdk"
import { toast } from "sonner"
import ErrorBanner from "@/components/ui/error-banner"
import { Button } from "@/components/ui/button"
import SelectedTransactionDetails from "./SelectedTransactionDetails"
import { AccountFormField, CurrencyFormField, DataField, DateField, LinkFormField, PartyTypeFormField, SmallTextField } from "@/components/ui/form-elements"
import { Form } from "@/components/ui/form"
import { useCallback, useContext, useMemo, useRef, useState } from "react"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Checkbox } from "@/components/ui/checkbox"
import { ArrowDownRight, ArrowUpRight, Plus, Trash2 } from "lucide-react"
import { flt, formatCurrency } from "@/lib/numbers"
import { cn } from "@/lib/utils"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import SelectedTransactionsTable from "./SelectedTransactionsTable"
import { JournalEntryAccount } from "@/types/Accounts/JournalEntryAccount"
import { BankTransaction } from "@/types/Accounts/BankTransaction"
import FileUploadBanner from "@/components/common/FileUploadBanner"
import { Label } from "@/components/ui/label"
import { FileDropzone } from "@/components/ui/file-dropzone"
import { useGetAccounts } from "@/components/common/AccountsDropdown"
import { useHotkeys } from "react-hotkeys-hook"
const BankEntryModal = () => {
const [isOpen, setIsOpen] = useAtom(bankRecRecordJournalEntryModalAtom)
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className='min-w-[95vw]'>
<DialogHeader>
<DialogTitle>{_("Bank Entry")}</DialogTitle>
<DialogDescription>
{_("Record a journal entry for expenses, income or split transactions.")}
</DialogDescription>
</DialogHeader>
<RecordBankEntryModalContent />
</DialogContent>
</Dialog>
)
}
const RecordBankEntryModalContent = () => {
const selectedBankAccount = useAtomValue(selectedBankAccountAtom)
const selectedTransaction = useAtomValue(bankRecSelectedTransactionAtom(selectedBankAccount?.name ?? ''))
if (!selectedTransaction || !selectedBankAccount || selectedTransaction.length === 0) {
return <div className='p-4'>
<span className='text-center'>{_("No transaction selected")}</span>
</div>
}
if (selectedTransaction.length === 1) {
return <BankEntryForm
selectedTransaction={selectedTransaction[0]} />
}
return <BulkBankEntryForm
selectedTransactions={selectedTransaction}
/>
}
const BulkBankEntryForm = ({ selectedTransactions }: { selectedTransactions: UnreconciledTransaction[] }) => {
const form = useForm<{
account: string
}>({
defaultValues: {
account: ''
}
})
const { call, loading, error } = useFrappePostCall<{ message: { transaction: BankTransaction, journal_entry: JournalEntry }[] }>('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_bulk_bank_entry_and_reconcile')
const onReconcile = useRefreshUnreconciledTransactions()
const addToActionLog = useUpdateActionLog()
const setIsOpen = useSetAtom(bankRecRecordJournalEntryModalAtom)
const onSubmit = (data: { account: string }) => {
call({
bank_transactions: selectedTransactions.map(transaction => transaction.name),
account: data.account
}).then(({ message }) => {
addToActionLog({
type: 'bank_entry',
timestamp: (new Date()).getTime(),
isBulk: true,
items: message.map((item) => ({
bankTransaction: item.transaction,
voucher: {
reference_doctype: "Journal Entry",
reference_name: item.journal_entry.name,
doc: item.journal_entry,
posting_date: item.journal_entry.posting_date,
}
})),
bulkCommonData: {
account: data.account,
}
})
toast.success(_("Bank Entries Created"), {
duration: 4000,
})
// Set this to the last selected transaction
onReconcile(selectedTransactions[selectedTransactions.length - 1])
setIsOpen(false)
})
}
return <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex flex-col gap-4">
{error && <ErrorBanner error={error} />}
<SelectedTransactionsTable />
<div className="grid grid-cols-3 gap-4">
<AccountFormField
name='account'
filterFunction={(acc) => {
// Do not allow payable and receivable accounts
return acc.account_type !== 'Payable' && acc.account_type !== 'Receivable'
}}
label={_('Account')}
isRequired
/>
</div>
<DialogFooter>
<DialogClose asChild>
<Button size='md' variant={'outline'} disabled={loading}>{_("Cancel")}</Button>
</DialogClose>
<Button size='md' type='submit' disabled={loading}>{_("Submit")}</Button>
</DialogFooter>
</div>
</form>
</Form>
}
interface BankEntryFormData extends Pick<JournalEntry, 'voucher_type' | 'cheque_date' | 'posting_date' | 'cheque_no' | 'user_remark'> {
entries: JournalEntry['accounts']
}
const BankEntryForm = ({ selectedTransaction }: { selectedTransaction: UnreconciledTransaction }) => {
const selectedBankAccount = useAtomValue(selectedBankAccountAtom)
const { data: rule } = useGetRuleForTransaction(selectedTransaction)
const setIsOpen = useSetAtom(bankRecRecordJournalEntryModalAtom)
const onClose = () => {
setIsOpen(false)
}
const isWithdrawal = (selectedTransaction.withdrawal && selectedTransaction.withdrawal > 0) ? true : false
const defaultAccounts = useMemo(() => {
const isWithdrawal = (selectedTransaction.withdrawal && selectedTransaction.withdrawal > 0) ? true : false
const accounts: Partial<JournalEntryAccount>[] = [
{
account: selectedBankAccount?.account ?? '',
bank_account: selectedTransaction.bank_account,
// Bank is debited if it's a deposit
debit: isWithdrawal ? 0 : selectedTransaction.unallocated_amount,
credit: isWithdrawal ? selectedTransaction.unallocated_amount : 0,
party_type: '',
party: '',
cost_center: ''
}]
// If there is no rule, we can just add the entries for the bank account transaction and the other side will be the reverse
if (!rule) {
accounts.push(
{
account: '',
// Amounts will be the reverse of the bank account transaction
debit: isWithdrawal ? selectedTransaction.unallocated_amount : 0,
credit: isWithdrawal ? 0 : selectedTransaction.unallocated_amount,
cost_center: getCompanyCostCenter(selectedTransaction.company ?? '') ?? '',
}
)
} else {
// Rule exists, so we need to check the type of rule
if (!rule.bank_entry_type || rule.bank_entry_type === "Single Account") {
// Only a single account needs to be added
accounts.push({
account: rule.account ?? '',
// Amounts will be the reverse of the bank account transaction
debit: isWithdrawal ? selectedTransaction.unallocated_amount : 0,
credit: isWithdrawal ? 0 : selectedTransaction.unallocated_amount,
cost_center: getCompanyCostCenter(selectedTransaction.company ?? '') ?? '',
})
} else {
// For multiple accounts, we need to loop over and add entries for each
// The last row will just be the remaining amount
let hasTotallyEmptyRowEarlier = false;
let totalDebits = isWithdrawal ? 0 : selectedTransaction.unallocated_amount ?? 0
let totalCredits = isWithdrawal ? selectedTransaction.unallocated_amount ?? 0 : 0
for (let i = 0; i < (rule.accounts?.length ?? 0); i++) {
const acc = rule.accounts?.[i]
// If it's the last row, add the difference amount
if (i === (rule.accounts?.length ?? 0) - 1 && !hasTotallyEmptyRowEarlier) {
const differenceAmount = flt(totalDebits - totalCredits, 2)
accounts.push({
account: acc?.account ?? '',
debit: differenceAmount > 0 ? 0 : Math.abs(differenceAmount),
credit: differenceAmount > 0 ? Math.abs(differenceAmount) : 0,
cost_center: getCompanyCostCenter(selectedTransaction.company ?? '') ?? '',
user_remark: acc?.user_remark ?? '',
})
} else {
/**
* The debit and credit amounts can also be expressions - like "transaction_amount * 0.5"
* So we need to compute the value of the expression
* We can use the eval function to do this. But we need to expose certain variables to the expression.
* One of them is transaction_amount which is the unallocated amount of the selected transaction
* @param expression - The expression to compute
* @returns The computed value
*/
const computeExpression = (expression: string) => {
const script = `
const transaction_amount = ${selectedTransaction.unallocated_amount ?? 0}
${expression};
`
let value = 0;
try {
value = window.eval(script);
} catch (error: unknown) {
console.error(error);
value = 0;
}
return value;
}
if (!acc?.debit && !acc?.credit) {
hasTotallyEmptyRowEarlier = true;
}
const computedDebit = acc?.debit ? flt(computeExpression(acc.debit), 2) : 0
const computedCredit = acc?.credit ? flt(computeExpression(acc.credit), 2) : 0
totalDebits = flt(totalDebits + computedDebit, 2)
totalCredits = flt(totalCredits + computedCredit, 2)
accounts.push({
account: acc?.account ?? '',
debit: computedDebit,
credit: computedCredit,
cost_center: getCompanyCostCenter(selectedTransaction.company ?? '') ?? '',
user_remark: acc?.user_remark ?? '',
})
}
}
}
}
return accounts
}, [rule, selectedTransaction, selectedBankAccount])
const form = useForm<BankEntryFormData>({
defaultValues: {
voucher_type: selectedBankAccount?.is_credit_card ? 'Credit Card Entry' : 'Bank Entry',
cheque_date: selectedTransaction.date,
posting_date: selectedTransaction.date,
cheque_no: (selectedTransaction.reference_number || selectedTransaction.description || '').slice(0, 140),
user_remark: selectedTransaction.description,
entries: defaultAccounts,
}
})
const onReconcile = useRefreshUnreconciledTransactions()
const { call: createBankEntry, loading, error, isCompleted } = useFrappePostCall<{ message: { transaction: BankTransaction, journal_entry: JournalEntry } }>('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_bank_entry_and_reconcile')
const setBankRecUnreconcileModalAtom = useSetAtom(bankRecUnreconcileModalAtom)
const addToActionLog = useUpdateActionLog()
const { file: frappeFile } = useContext(FrappeContext) as FrappeConfig
const [isUploading, setIsUploading] = useState(false)
const [uploadProgress, setUploadProgress] = useState(0)
const [files, setFiles] = useState<File[]>([])
const onSubmit = (data: BankEntryFormData) => {
createBankEntry({
bank_transaction_name: selectedTransaction.name,
...data
}).then(async ({ message }) => {
addToActionLog({
type: 'bank_entry',
isBulk: false,
timestamp: (new Date()).getTime(),
items: [
{
bankTransaction: message.transaction,
voucher: {
reference_doctype: "Journal Entry",
reference_name: message.journal_entry.name,
reference_no: message.journal_entry.cheque_no,
reference_date: message.journal_entry.cheque_date,
posting_date: message.journal_entry.posting_date,
doc: message.journal_entry,
}
}
]
})
toast.success(_("Bank Entry Created"), {
duration: 4000,
closeButton: true,
action: {
label: _("Undo"),
onClick: () => setBankRecUnreconcileModalAtom(selectedTransaction.name)
},
actionButtonStyle: {
backgroundColor: "rgb(0, 138, 46)"
}
})
if (files.length > 0) {
setIsUploading(true)
const uploadPromises = files.map(f => {
return frappeFile.uploadFile(f, {
isPrivate: true,
doctype: "Journal Entry",
docname: message.journal_entry.name,
}, (_bytesUploaded, _totalBytes, progress) => {
setUploadProgress((currentProgress) => {
//If there are multiple files, we need to add the progress to the current progress
return currentProgress + ((progress?.progress ?? 0) / files.length)
})
})
})
return Promise.all(uploadPromises).then(() => {
setUploadProgress(0)
setIsUploading(false)
}).catch((error) => {
console.error(error)
toast.error(_("Error uploading attachments"), {
duration: 4000,
})
setIsUploading(false)
})
} else {
return Promise.resolve()
}
}).then(() => {
onReconcile(selectedTransaction)
onClose()
})
}
useHotkeys('meta+s', () => {
form.handleSubmit(onSubmit)()
}, {
enabled: true,
preventDefault: true,
enableOnFormTags: true
})
if (isUploading && isCompleted) {
return <FileUploadBanner uploadProgress={uploadProgress} />
}
return <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className='flex flex-col gap-4'>
{error && <ErrorBanner error={error} />}
<div className='grid grid-cols-2 gap-4'>
<SelectedTransactionDetails transaction={selectedTransaction} />
<div className='flex flex-col gap-4'>
<div className='grid grid-cols-2 gap-4'>
<DateField
name='posting_date'
label={_("Posting Date")}
isRequired
inputProps={{ autoFocus: false }}
/>
<DateField
name='cheque_date'
label={_("Reference Date")}
isRequired
inputProps={{ autoFocus: false }}
rules={{
required: _("Reference Date is required"),
}}
/>
</div>
<DataField name='cheque_no' label={_("Reference")} isRequired inputProps={{ autoFocus: false }}
rules={{
required: _("Reference is required"),
}} />
</div>
</div>
<div>
<Entries company={selectedTransaction.company ?? ''} isWithdrawal={isWithdrawal} currency={selectedTransaction.currency ?? getCompanyCurrency(selectedTransaction.company ?? '')} />
</div>
<div className='flex flex-col gap-2'>
<div className='grid grid-cols-2 gap-4'>
<SmallTextField
name='user_remark'
label={_("Remarks")}
/>
<div
data-slot="form-item"
className="flex flex-col gap-2"
>
<Label>{_("Attachments")}</Label>
<FileDropzone files={files} setFiles={setFiles} />
</div>
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button size='md' variant={'outline'} disabled={loading}>{_("Cancel")}</Button>
</DialogClose>
<Button size='md' type='submit' disabled={loading}>{_("Submit")}</Button>
</DialogFooter>
</div>
</form>
</Form>
}
const Entries = ({ company, isWithdrawal, currency }: { company: string, isWithdrawal: boolean, currency: string }) => {
const { getValues, setValue, control } = useFormContext<BankEntryFormData>()
const { call } = useContext(FrappeContext) as FrappeConfig
const partyMapRef = useRef<Record<string, string>>({})
const onPartyChange = (value: string, index: number) => {
// Get the account for the party type
if (value) {
if (partyMapRef.current[value]) {
setValue(`entries.${index}.account`, partyMapRef.current[value])
} else {
call.get('erpnext.accounts.party.get_party_account', {
party: value,
party_type: getValues(`entries.${index}.party_type`),
company: company
}).then((result: { message: string }) => {
setValue(`entries.${index}.account`, result.message)
partyMapRef.current[value] = result.message
})
}
} else {
setValue(`entries.${index}.account`, '')
}
}
const { data: accounts } = useGetAccounts()
const onAccountChange = (value: string, index: number) => {
// If it's an income or expense account, get the default cost center
if (value) {
const account = accounts?.find((acc) => acc.name === value)
if (account && account.report_type === "Profit and Loss") {
// Set the default company cost center
setValue(`entries.${index}.cost_center`, getCompanyCostCenter(company) ?? '')
return
}
}
setValue(`entries.${index}.cost_center`, '')
}
const { fields, append, remove } = useFieldArray({
control: control,
name: 'entries'
})
const onAdd = useCallback(() => {
const existingEntries = getValues('entries')
const totalDebits = existingEntries.reduce((acc, curr) => flt(acc + (curr.debit ?? 0), 2), 0)
const totalCredits = existingEntries.reduce((acc, curr) => flt(acc + (curr.credit ?? 0), 2), 0)
const remainingAmount = flt(totalDebits - totalCredits, 2)
// Remaining amount is credit if it's positive - since some debit is pending to be cleared.
const debitAmount = remainingAmount > 0 ? 0 : Math.abs(remainingAmount)
const creditAmount = remainingAmount > 0 ? Math.abs(remainingAmount) : 0
append({
party_type: '',
party: '',
account: '',
debit: debitAmount,
credit: creditAmount,
cost_center: getCompanyCostCenter(company) ?? ''
} as JournalEntryAccount, {
focusName: `entries.${existingEntries.length}.account`
})
}, [company, append, getValues])
const [selectedRows, setSelectedRows] = useState<number[]>([])
const onSelectRow = useCallback((index: number) => {
setSelectedRows(prev => {
if (prev.includes(index)) {
return prev.filter(i => i !== index)
}
return [...prev, index]
})
}, [])
const onSelectAll = useCallback(() => {
setSelectedRows(prev => {
if (prev.length === fields.length) {
return []
}
return [...fields.map((_, index) => index)]
})
}, [fields])
const onRemove = useCallback(() => {
remove(selectedRows)
setSelectedRows([])
}, [remove, selectedRows])
/**
* When add difference is clicked, check if the last row has nothing filled in.
* If last row is empty (no debit or credit), then set that row's amount. Else, add a new row with the difference amount.
*/
const onAddDifferenceClicked = () => {
const existingEntries = getValues('entries')
const totalDebits = existingEntries.reduce((acc, curr) => flt(acc + (curr.debit ?? 0), 2), 0)
const totalCredits = existingEntries.reduce((acc, curr) => flt(acc + (curr.credit ?? 0), 2), 0)
const lastIndex = existingEntries.length - 1
const isLastRowEmpty = (existingEntries[lastIndex]?.debit === 0 || existingEntries[lastIndex]?.debit === undefined) && (existingEntries[lastIndex]?.credit === 0 || existingEntries[lastIndex]?.credit === undefined)
const remainingAmount = flt(totalDebits - totalCredits, 2)
// Remaining amount is credit if it's positive - since some debit is pending to be cleared.
const debitAmount = remainingAmount > 0 ? 0 : Math.abs(remainingAmount)
const creditAmount = remainingAmount > 0 ? Math.abs(remainingAmount) : 0
if (isLastRowEmpty) {
setValue(`entries.${lastIndex}.debit`, debitAmount)
setValue(`entries.${lastIndex}.credit`, creditAmount)
} else {
append({
party_type: '',
party: '',
account: '',
debit: debitAmount,
credit: creditAmount,
cost_center: getCompanyCostCenter(company) ?? ''
} as JournalEntryAccount, {
focusName: `entries.${existingEntries.length}.account`
})
}
}
return <div className="flex flex-col gap-2">
<Table>
<TableHeader>
<TableRow>
<TableHead><Checkbox
disabled={fields.length === 0}
// Make this accessible to screen readers
aria-label={_("Select all")}
checked={selectedRows.length > 0 && selectedRows.length === fields.length}
onCheckedChange={onSelectAll} /></TableHead>
<TableHead>{_("Party")}</TableHead>
<TableHead>{_("Account")}</TableHead>
<TableHead>{_("Cost Center")}</TableHead>
<TableHead>{_("Remarks")}</TableHead>
<TableHead className="text-end">{_("Debit")}</TableHead>
<TableHead className="text-end">{_("Credit")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{fields.map((field, index) => (
<TableRow key={field.id} className={index === 0 ? 'bg-surface-gray-1 cursor-not-allowed' : ''} title={index === 0 ? _("This is the bank account entry. You cannot edit it.") : ''}>
<TableCell>
<Checkbox
checked={selectedRows.includes(index)}
onCheckedChange={() => onSelectRow(index)}
// Make this accessible to screen readers
aria-label={_("Select row {0}", [String(index + 1)])}
disabled={index === 0}
/>
</TableCell>
<TableCell className="align-top">
<div className="flex">
<PartyTypeFormField
name={`entries.${index}.party_type`}
label={_("Party Type")}
isRequired
readOnly={index === 0}
hideLabel
inputProps={{
type: isWithdrawal ? 'Payable' : 'Receivable',
triggerProps: {
className: 'rounded-e-none',
tabIndex: -1
},
readOnly: index === 0,
}} />
<PartyField index={index} onChange={onPartyChange} readOnly={index === 0} />
</div>
</TableCell>
<TableCell className="align-top">
<AccountFormField
name={`entries.${index}.account`}
label={_("Account")}
rules={{
required: _("Account is required"),
onChange: (event) => {
onAccountChange(event.target.value, index)
}
}}
buttonClassName="min-w-64"
readOnly={index === 0}
isRequired
hideLabel
/>
</TableCell>
<TableCell className="align-top">
<LinkFormField
doctype="Cost Center"
name={`entries.${index}.cost_center`}
label={_("Cost Center")}
filters={[["company", "=", company], ["is_group", "=", 0], ["disabled", "=", 0]]}
buttonClassName="min-w-48"
readOnly={index === 0}
hideLabel
/>
</TableCell>
<TableCell className="align-top">
<DataField
name={`entries.${index}.user_remark`}
label={_("Remarks")}
readOnly={index === 0}
inputProps={{
placeholder: _("e.g. Bank Charges"),
className: 'min-w-64',
readOnly: index === 0
}}
hideLabel
/>
</TableCell>
<TableCell className={cn("text-end align-top")}>
<CurrencyFormField
name={`entries.${index}.debit`}
label={_("Debit")}
isRequired
hideLabel
readOnly={index === 0}
style={index === 0 ? !isWithdrawal ? {
color: "var(--color-ink-gray-8)",
} : {} : {}}
currency={currency}
leftSlot={index === 0 && !isWithdrawal ? <Tooltip>
<TooltipTrigger asChild><ArrowDownRight className="text-ink-green-3" /></TooltipTrigger>
<TooltipContent>{_("Bank account debit for deposit")}</TooltipContent>
</Tooltip> : undefined}
/>
</TableCell>
<TableCell className={cn("text-end align-top")}>
<CurrencyFormField
name={`entries.${index}.credit`}
style={index === 0 && isWithdrawal ? {
color: "var(--color-ink-gray-8)",
} : {}}
label={_("Credit")}
isRequired
hideLabel
readOnly={index === 0}
currency={currency}
leftSlot={index === 0 && isWithdrawal ? <Tooltip>
<TooltipTrigger asChild><ArrowUpRight className="text-ink-red-3" /></TooltipTrigger>
<TooltipContent>{_("Bank account credit for withdrawal")}</TooltipContent>
</Tooltip> : undefined}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<div className="flex justify-between gap-2">
<div className="flex gap-2 justify-end">
<div>
<Button size='sm' type='button' variant={'outline'} onClick={onAdd}><Plus /> {_("Add Row")}</Button>
</div>
{selectedRows.length > 0 && <div>
<Button size='sm' type='button' theme="red" onClick={onRemove}><Trash2 /> {_("Remove")}</Button>
</div>}
</div>
<Summary currency={currency} addRow={onAddDifferenceClicked} />
</div>
</div>
}
const PartyField = ({ index, onChange, readOnly }: { index: number, onChange: (value: string, index: number) => void, readOnly: boolean }) => {
const { control } = useFormContext<BankEntryFormData>()
const party_type = useWatch({
control,
name: `entries.${index}.party_type`
})
if (!party_type) {
return <DataField
name={`entries.${index}.party`}
label={_("Party")}
isRequired
inputProps={{
disabled: true,
className: 'rounded-s-none border-s-0 min-w-64'
}}
hideLabel
/>
}
return <LinkFormField
name={`entries.${index}.party`}
label={_("Party")}
rules={{
onChange: (event) => {
onChange(event.target.value, index)
},
}}
hideLabel
readOnly={readOnly}
buttonClassName="rounded-s-none border-s-0 min-w-64"
doctype={party_type}
/>
}
const Summary = ({ currency, addRow }: { currency: string, addRow: () => void }) => {
const { control } = useFormContext<BankEntryFormData>()
const entries = useWatch({ control, name: 'entries' })
const { total, totalCredits, totalDebits } = useMemo(() => {
// Do a total debits - total credits
const totalDebits = entries.reduce((acc, curr) => flt(acc + (curr.debit ?? 0), 2), 0)
const totalCredits = entries.reduce((acc, curr) => flt(acc + (curr.credit ?? 0), 2), 0)
return { total: flt(totalDebits - totalCredits, 2), totalDebits, totalCredits }
}, [entries])
const onAddRow = useCallback(() => {
addRow()
}, [addRow])
const TextComponent = ({ className, children }: { className?: string, children: React.ReactNode }) => {
return <span className={cn("w-32 text-end font-medium text-sm font-numeric", className)}>{children}</span>
}
return <div className="flex flex-col gap-2 items-end">
<div className="flex gap-2 justify-between">
<TextComponent>{_("Total Debit")}</TextComponent>
<TextComponent>{formatCurrency(totalDebits, currency)}</TextComponent>
</div>
<div className="flex gap-2 justify-between">
<TextComponent>{_("Total Credit")}</TextComponent>
<TextComponent>{formatCurrency(totalCredits, currency)}</TextComponent>
</div>
{total !== 0 && <div className="flex gap-2 justify-between">
<TextComponent>{_("Difference")}</TextComponent>
<Tooltip>
<TooltipTrigger asChild>
<Button type='button' variant='link' className="p-0 text-ink-red-3 underline h-fit" role='button' onClick={onAddRow}>
<TextComponent className='text-ink-red-3'>{formatCurrency(total, currency)}</TextComponent>
</Button>
</TooltipTrigger>
<TooltipContent>
{_("Add a row with the difference amount")}
</TooltipContent>
</Tooltip>
</div>}
</div>
}
export default BankEntryModal

View File

@@ -0,0 +1,134 @@
import { useAtom } from "jotai"
import { SelectedBank, selectedBankAccountAtom } from "./bankRecAtoms"
import { useCallback } from "react"
import { useGetBankAccounts, useGetUnreconciledTransactions } from "./utils"
import { cn } from "@/lib/utils"
import { getTimeago } from "@/lib/date"
import ErrorBanner from "@/components/ui/error-banner"
import _ from "@/lib/translate"
import { Badge } from "@/components/ui/badge"
import { useTheme } from "@/components/ui/theme-provider"
import BankLogo from "@/components/common/BankLogo"
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"
import { LandmarkIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useCurrentCompany } from "@/hooks/useCurrentCompany"
const BankPicker = ({ className }: { className?: string }) => {
const [selectedBank, setSelectedBank] = useAtom(selectedBankAccountAtom)
const onLoadingSuccess = useCallback((data?: SelectedBank[]) => {
// If the bank is already selected, then don't set it again
if (selectedBank) {
// Check if selected bank is in the data
if (data?.some((bank: SelectedBank) => bank.name === selectedBank.name)) {
return
}
}
if (!data) return
if (data.length === 1) {
setSelectedBank(data[0])
} else if (data.length > 1) {
const defaultBank = data.find((bank: SelectedBank) => bank.is_default)
if (defaultBank) {
setSelectedBank(defaultBank)
} else {
// Select the first available bank account
setSelectedBank(data[0])
}
}
}, [setSelectedBank, selectedBank])
const selectedCompany = useCurrentCompany()
const { banks, isLoading, error } = useGetBankAccounts(onLoadingSuccess)
const { themeValue } = useTheme()
if (isLoading) {
return null
}
if (error) {
return <ErrorBanner error={error} />
}
if (banks?.length === 0) {
return <Empty>
<EmptyMedia>
<LandmarkIcon />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{_("No bank accounts found")}</EmptyTitle>
<EmptyDescription>{_("You have not added any bank accounts to your company.")}</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button asChild>
<a href={`/desk/bank-account?company=${encodeURIComponent(selectedCompany)}&is_company_account=1`}>
{_("Configure Bank Accounts")}
</a>
</Button>
</EmptyContent>
</Empty>
}
return (
<div
className={cn("flex gap-3 items-stretch w-full overflow-x-auto pe-4",
banks?.length > 4 ? 'pb-2' : '', className,
)}
style={{
scrollbarWidth: 'thin',
scrollbarColor: themeValue === 'Dark' ? 'var(--surface-gray-2) var(--surface-gray-1)' : 'rgb(209 213 219) rgb(243 244 246)',
}}
>
{
banks?.map((bank) => (
<BankPickerItem key={bank.name} bank={bank} />
))
}
</div>
)
}
const BankPickerItem = ({ bank }: { bank: SelectedBank }) => {
const [selectedBank, setSelectedBank] = useAtom(selectedBankAccountAtom)
const isSelected = selectedBank?.name === bank.name
const { mutate } = useGetUnreconciledTransactions()
const onSelect = () => {
setSelectedBank(bank)
mutate()
}
return <div
role="button"
title={`Select ${bank.account_name}`}
onClick={onSelect}
className={cn('rounded-md border border-outline-gray-1 max-w-60 min-w-60 p-2 overflow-hidden cursor-pointer',
isSelected ? 'border-outline-gray-5 bg-surface-gray-1' : 'hover:bg-surface-gray-1'
)}
>
<BankLogo bank={bank} className="mb-2" />
<div className="flex flex-col gap-1">
<div className="flex gap-2 items-center">
<span className={cn("text-sm font-medium line-clamp-1 text-ink-gray-8")}>{bank.account_name}</span>
{bank.account_type && <Badge variant='subtle' size='sm' theme='gray'>
{bank.account_type?.slice(0, 24)}
</Badge>}
</div>
<span title={_("GL Account")} className={cn("text-ellipsis line-clamp-1 text-sm text-ink-gray-6")}>{bank.account}</span>
{bank.last_integration_date && <span className="text-xs text-ink-gray-5">{_("Last Synced Transaction")}: {getTimeago(bank.last_integration_date)}</span>}
</div>
</div >
}
export default BankPicker

View File

@@ -0,0 +1,275 @@
import { useAtom } from 'jotai'
import { bankRecDateAtom } from './bankRecAtoms'
import { useMemo, useState } from 'react'
import { AVAILABLE_TIME_PERIODS, formatDate, getDatesForTimePeriod, TimePeriod } from '@/lib/date'
import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { ChevronDownIcon, ChevronLeftIcon, ChevronRight } from 'lucide-react'
import { Command, CommandEmpty, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
import { parse } from "chrono-node"
import { Calendar } from '@/components/ui/calendar'
import useFiscalYear from '@/hooks/useFiscalYear'
import dayjs from 'dayjs'
import _ from '@/lib/translate'
import { useDirection } from '@/components/ui/direction'
const BankRecDateFilter = () => {
const [bankRecDate, setBankRecDate] = useAtom(bankRecDateAtom)
const { data: fiscalYear } = useFiscalYear()
const timePeriodOptions = useMemo(() => {
const standardOptions = AVAILABLE_TIME_PERIODS.map((period) => {
const dates = getDatesForTimePeriod(period)
return {
label: period,
fromDate: dates.fromDate,
toDate: dates.toDate,
format: dates.format,
translatedLabel: dates.translatedLabel
}
})
if (fiscalYear?.message) {
// For a fiscal year, we need to replace "Last Year", "This Year", and add options for quarters
const fiscalYearStart = fiscalYear.message.year_start_date
const fiscalYearEnd = fiscalYear.message.year_end_date
const q1 = {
label: `Q1: ${fiscalYear.message.name}`,
translatedLabel: `${_("Q1")}: ${fiscalYear.message.name}`,
fromDate: fiscalYearStart,
toDate: dayjs(fiscalYearStart).add(3, 'month').format('YYYY-MM-DD'),
format: 'MMM YYYY'
}
const q2 = {
label: `Q2: ${fiscalYear.message.name}`,
translatedLabel: `${_("Q2")}: ${fiscalYear.message.name}`,
fromDate: dayjs(fiscalYearStart).add(3, 'month').format('YYYY-MM-DD'),
toDate: dayjs(fiscalYearStart).add(6, 'month').format('YYYY-MM-DD'),
format: 'MMM YYYY'
}
const q3 = {
label: `Q3: ${fiscalYear.message.name}`,
translatedLabel: `${_("Q3")}: ${fiscalYear.message.name}`,
fromDate: dayjs(fiscalYearStart).add(6, 'month').format('YYYY-MM-DD'),
toDate: dayjs(fiscalYearStart).add(9, 'month').format('YYYY-MM-DD'),
format: 'MMM YYYY'
}
const q4 = {
label: `Q4: ${fiscalYear.message.name}`,
translatedLabel: `${_("Q4")}: ${fiscalYear.message.name}`,
fromDate: dayjs(fiscalYearStart).add(9, 'month').format('YYYY-MM-DD'),
toDate: fiscalYearEnd,
format: 'MMM YYYY'
}
const thisYear = {
label: `This Fiscal Year`,
translatedLabel: `${_("This Fiscal Year")}`,
fromDate: fiscalYearStart,
toDate: fiscalYearEnd,
format: 'MMM YYYY'
}
const lastYear = {
label: `Last Fiscal Year`,
translatedLabel: `${_("Last Fiscal Year")}`,
fromDate: dayjs(fiscalYearStart).subtract(1, 'year').format('YYYY-MM-DD'),
toDate: dayjs(fiscalYearEnd).subtract(1, 'year').format('YYYY-MM-DD'),
format: 'MMM YYYY'
}
// Sort the options so that we get "This Month", "Last Month", quarters, fiscal year, then the rest of the standard options
const topRankedItems = standardOptions.filter((option) => {
return option.label === "This Month" || option.label === "Last Month"
})
const bottomRankedItems = standardOptions.filter((option) => {
return option.label !== "This Month" && option.label !== "Last Month"
})
return [...topRankedItems, q1, q2, q3, q4, thisYear, lastYear, ...bottomRankedItems]
}
return standardOptions
}, [fiscalYear])
const [open, setOpen] = useState(false)
const [value, setValue] = useState("")
const timePeriod: TimePeriod | string = useMemo(() => {
if (bankRecDate.fromDate && bankRecDate.toDate) {
// Check if the from and to dates match any predefined time period
for (const period of timePeriodOptions) {
if (period.fromDate === bankRecDate.fromDate && period.toDate === bankRecDate.toDate) {
return period.label;
}
}
return "Date Range";
} else {
return "Date Range";
}
}, [bankRecDate.fromDate, bankRecDate.toDate, timePeriodOptions]);
const handleTimePeriodChange = (fromDate: string, toDate: string) => {
setBankRecDate({ fromDate, toDate })
setOpen(false)
}
const dateObj = useMemo(() => {
return {
from: new Date(bankRecDate.fromDate),
to: new Date(bankRecDate.toDate)
}
}, [bankRecDate.fromDate, bankRecDate.toDate])
const direction = useDirection()
return <div className='flex items-center'>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant={'outline'}
aria-expanded={open}
size='md'
className='rounded-e-none border-e-0'
role="combobox">
{timePeriodOptions.find((period) => period.label === timePeriod)?.translatedLabel ?? _(timePeriod)}
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent className="w-84 p-1" align='start'>
<Command>
<CommandInput placeholder="e.g. Last 3 weeks" onValueChange={setValue} value={value} />
<CommandList className='max-h-fit'>
<CommandEmpty className='text-start p-2 hover:bg-surface-gray-1'>
<EmptyState onSelect={handleTimePeriodChange} value={value} />
</CommandEmpty>
{timePeriodOptions.map((period) => (
<CommandItem key={period.label} className='flex justify-between' onSelect={() => handleTimePeriodChange(period.fromDate, period.toDate)}>
<span>
{period.translatedLabel ?? _(period.label)}
</span>
<span className='text-xs text-ink-gray-5 flex items-center gap-1 text-end whitespace-nowrap'>
{formatDate(period.fromDate, period.format)} {direction === 'ltr' ? <ChevronRight className='text-[12px] text-ink-gray-5/70' /> : <ChevronLeftIcon className='text-[12px] text-ink-gray-5/70' />} {formatDate(period.toDate, period.format)}
</span>
</CommandItem>
))}
</CommandList>
</Command>
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger asChild>
<Button variant={'outline'} className='rounded-s-none' size='md'>
{formatDate(bankRecDate.fromDate)} - {formatDate(bankRecDate.toDate)}
</Button>
</PopoverTrigger>
<PopoverContent className='w-auto overflow-hidden p-0' align='end'>
<Calendar
mode='range'
captionLayout='dropdown'
selected={{
from: dateObj.from,
to: dateObj.to
}}
numberOfMonths={2}
defaultMonth={dateObj.from}
onSelect={(date) => {
if (date) {
setBankRecDate({ fromDate: formatDate(date.from, 'YYYY-MM-DD'), toDate: formatDate(date.to, 'YYYY-MM-DD') })
}
}}
/>
</PopoverContent>
</Popover>
</div>
}
const referentialKeywords = ["last", "this", "next", "previous"]
const EmptyState = ({ onSelect, value }: { onSelect: (fromDate: string, toDate: string) => void, value: string }) => {
const dates = useMemo(() => {
if (value) {
// Try parsing the value
const parsedDate = parse(value, undefined, { forwardDate: false })
if (parsedDate && parsedDate.length > 0) {
const startDate = parsedDate[0].start.date()
const endDate = parsedDate[0].end?.date()
if (!endDate) {
const today = new Date()
// If today is greater than the start date, use today as the end date
if (startDate.getTime() > today.getTime()) {
return { fromDate: today, toDate: startDate }
} else {
// Check if the user only wants a specific month like "May 2025"
// If the "known values" just has month and year, then we need to get the first day of the month and the last day of the month
// @ts-expect-error - "Known Values" is available in the start "ParsingComponents"
if (parsedDate[0].start.knownValues?.month && !parsedDate[0].start.knownValues?.day) {
return {
fromDate: startDate,
toDate: dayjs(startDate).endOf('month').toDate()
}
// @ts-expect-error - "Known Values" is available in the start "ParsingComponents"
} else if (parsedDate[0].start.knownValues?.month && parsedDate[0].start.knownValues?.day && !referentialKeywords.some(keyword => value.toLowerCase().includes(keyword))) {
// If month and day is known, then we should not assume that the user wants to get everything until today
return {
fromDate: startDate,
toDate: startDate,
}
}
return {
fromDate: startDate,
toDate: today
}
}
} else {
return { fromDate: startDate, toDate: endDate }
}
}
}
}, [value])
const onClick = (fromDate: Date, toDate: Date) => {
onSelect(formatDate(fromDate, 'YYYY-MM-DD'), formatDate(toDate, 'YYYY-MM-DD'))
}
const isEqual = dates?.fromDate && dates?.toDate && dayjs(dates.fromDate).isSame(dates.toDate, 'date')
return <div>
{dates ?
<div className='flex gap-2 items-center justify-between cursor-pointer' onClick={() => onClick(dates.fromDate, dates.toDate)}>
<span className='text-sm text-ink-gray-5 max-w-[30%]'>
{value}
</span>
{isEqual ? <span className='text-xs text-ink-gray-5 text-balance flex items-center gap-1'>
{formatDate(dates.fromDate, 'Do MMM YYYY')}
</span> :
<span className='text-xs text-ink-gray-5 flex items-center gap-1'>
{formatDate(dates.fromDate, 'Do MMM YY')} <ChevronRight size='16' className='text-ink-gray-5' /> {formatDate(dates.toDate, 'Do MMM YY')}
</span>}
</div> :
<span className='text-sm text-ink-gray-5'>
No results found
</span>
}
</div>
}
export default BankRecDateFilter

View File

@@ -0,0 +1,315 @@
import { useAtomValue } from "jotai"
import { MissingFiltersBanner } from "./MissingFiltersBanner"
import { bankRecDateAtom, selectedBankAccountAtom } from "./bankRecAtoms"
import { useCurrentCompany } from "@/hooks/useCurrentCompany"
import { Paragraph } from "@/components/ui/typography"
import { useCallback, useMemo } from "react"
import type { ColumnDef } from "@tanstack/react-table"
import { useFrappeGetCall } from "frappe-react-sdk"
import { QueryReportReturnType } from "@/types/custom/Reports"
import { formatDate } from "@/lib/date"
import { ListView, type ListViewColumnMeta } from "@/components/ui/list-view"
import { formatCurrency } from "@/lib/numbers"
import { getCompanyCurrency } from "@/lib/company"
import { slug } from "@/lib/frappe"
import { ScrollTextIcon } from "lucide-react"
import ErrorBanner from "@/components/ui/error-banner"
import { StatContainer, StatLabel, StatValue } from "@/components/ui/stats"
import _ from "@/lib/translate"
import { toast } from "sonner"
import { useCopyToClipboard } from "usehooks-ts"
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"
const BankReconciliationStatement = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
if (!bankAccount) {
return <MissingFiltersBanner text={_("Please select a bank account to view the bank reconciliation statement.")} />
}
if (!dates) {
return <MissingFiltersBanner text={_("Please select dates to view the bank reconciliation statement.")} />
}
return <BankReconciliationStatementView />
}
interface BankClearanceSummaryEntry {
payment_document: string
payment_entry: string
posting_date: string,
reference_no: string,
credit: number,
debit: number,
against_account: string,
ref_date: string,
account_currency: string,
clearance_date: string
}
const BankReconciliationStatementView = () => {
const companyID = useCurrentCompany()
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
const filters = useMemo(() => {
return JSON.stringify({
account: bankAccount?.account,
report_date: dates.toDate,
company: companyID
})
}, [bankAccount, dates, companyID])
const { data, error } = useFrappeGetCall<{ message: QueryReportReturnType }>('frappe.desk.query_report.run', {
report_name: 'Bank Reconciliation Statement',
filters,
ignore_prepared_report: 1,
are_default_filters: false,
}, `Report-Bank Reconciliation Statement-${filters}`, { keepPreviousData: true, revalidateOnFocus: false }, 'POST')
const [, copyToClipboard] = useCopyToClipboard()
const onCopy = useCallback(
(text: string) => {
copyToClipboard(text).then(() => {
toast.success(_("Copied to clipboard"))
})
},
[copyToClipboard, _],
)
const statementColumns = useMemo<ColumnDef<BankClearanceSummaryEntry, unknown>[]>(
() => [
{
accessorKey: "posting_date",
header: _("Posting Date"),
size: 118,
meta: { tabularNums: true } satisfies ListViewColumnMeta,
cell: ({ row }) => formatDate(row.original.posting_date),
},
{
accessorKey: "payment_document",
header: _("Document Type"),
size: 140,
cell: ({ row }) => _(row.original.payment_document),
},
{
id: "payment_entry",
header: _("Payment Document"),
size: 300,
meta: {
getTooltipText: (r) => {
const x = r as BankClearanceSummaryEntry
const parts = [x.payment_document, x.payment_entry].filter(Boolean)
return parts.length ? parts.join(" · ") : undefined
},
} satisfies ListViewColumnMeta,
cell: ({ row }) => {
const { payment_document, payment_entry } = row.original
return payment_document ? (
<a
target="_blank"
rel="noreferrer"
className="text-ink-gray-8 block min-w-0 w-full underline underline-offset-4"
href={`/desk/${slug(payment_document)}/${payment_entry}`}
>
{payment_entry}
</a>
) : (
payment_entry
)
},
},
{
accessorKey: "debit",
header: _("Debit"),
size: 112,
meta: { align: "right" } satisfies ListViewColumnMeta,
cell: ({ row }) => <span className="font-numeric">{formatCurrency(row.original.debit, row.original.account_currency)}</span>,
},
{
accessorKey: "credit",
header: _("Credit"),
size: 112,
meta: { align: "right" } satisfies ListViewColumnMeta,
cell: ({ row }) => <span className="font-numeric">{formatCurrency(row.original.credit, row.original.account_currency)}</span>,
},
{
accessorKey: "against_account",
header: _("Against Account"),
meta: { gridWidth: "minmax(0,1.25fr)" } satisfies ListViewColumnMeta,
cell: ({ row }) => (
<a
target="_blank"
rel="noreferrer"
className="text-ink-gray-8 block min-w-0 w-full underline underline-offset-4"
href={`/desk/account/${row.original.against_account}`}
>
{row.original.against_account}
</a>
),
},
{
accessorKey: "reference_no",
header: _("Reference #"),
cell: ({ row }) => {
const ref = row.original.reference_no
return (
<button
type="button"
className="text-ink-gray-8 hover:underline min-w-0 w-full cursor-pointer truncate text-start underline-offset-4"
onClick={() => onCopy(ref)}
>
{ref}
</button>
)
},
},
{
accessorKey: "ref_date",
header: _("Reference Date"),
size: 118,
meta: { tabularNums: true } satisfies ListViewColumnMeta,
cell: ({ row }) => formatDate(row.original.ref_date),
},
{
accessorKey: "clearance_date",
header: _("Clearance Date"),
size: 118,
meta: { tabularNums: true } satisfies ListViewColumnMeta,
cell: ({ row }) => formatDate(row.original.clearance_date),
},
],
[_, onCopy],
)
const statementRows = useMemo(() => {
if (!data?.message.result) return []
return data.message.result.filter((row: BankClearanceSummaryEntry) => Boolean(row.payment_entry))
}, [data])
return <div className="space-y-4 py-2">
<div>
<Paragraph className="text-sm">
<span dangerouslySetInnerHTML={{
__html: _("Below is a list of all entries posted against the bank account {0} which have not been cleared till {1}.", [`<strong>${bankAccount?.account}</strong>`, `<strong>${formatDate(dates.toDate)}</strong>`])
}} />
</Paragraph>
</div>
{error && <ErrorBanner error={error} />}
{data && <SummarySection data={data} />}
{data && data.message.result.length > 0 && (
<div className="space-y-2">
<p className="text-ink-gray-5 text-sm">{_("Bank Reconciliation Statement")}</p>
<ListView
data={statementRows}
columns={statementColumns}
getRowId={(row) => row.payment_entry}
maxHeight="min(70vh, 640px)"
emptyState={_("No entries with a payment document in this list.")}
/>
</div>
)}
{data && data.message.result.length === 0 &&
<Empty>
<EmptyMedia>
<ScrollTextIcon />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{_("No entries found")}</EmptyTitle>
<EmptyDescription>{_("There are no accounting entries in the system for the selected account and dates.")}</EmptyDescription>
</EmptyHeader>
</Empty>
}
</div>
}
const SummarySection = ({ data }: { data: { message: QueryReportReturnType } }) => {
const company = useCurrentCompany()
const bankAccount = useAtomValue(selectedBankAccountAtom)
const { bankStatementBalanceAsPerGL, outstandingChecksDebit, outstandingChecksCredit, incorrectlyClearedEntriesDebit, incorrectlyClearedEntriesCredit, calculatedBankStatementBalance } = useMemo(() => {
// Loop over the results and find the corresponding rows
let bankStatementBalanceAsPerGL = 0
let outstandingChecksDebit = 0
let outstandingChecksCredit = 0
let incorrectlyClearedEntriesDebit = 0
let incorrectlyClearedEntriesCredit = 0
let calculatedBankStatementBalance = 0
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data?.message.result.forEach((r: any) => {
if (r.payment_entry === 'Bank Statement balance as per General Ledger') {
bankStatementBalanceAsPerGL = r.debit - r.credit
}
if (r.payment_entry === 'Outstanding Checks and Deposits to clear') {
outstandingChecksDebit = r.debit
outstandingChecksCredit = r.credit
}
if (r.payment_entry === 'Checks and Deposits incorrectly cleared') {
incorrectlyClearedEntriesDebit = r.debit
incorrectlyClearedEntriesCredit = r.credit
}
if (r.payment_entry === 'Calculated Bank Statement balance') {
calculatedBankStatementBalance = r.debit - r.credit
}
})
return {
bankStatementBalanceAsPerGL,
outstandingChecksDebit,
outstandingChecksCredit,
incorrectlyClearedEntriesDebit,
incorrectlyClearedEntriesCredit,
calculatedBankStatementBalance
}
}, [data])
const currency = bankAccount?.account_currency ?? getCompanyCurrency(company)
return <div className="flex gap-4 items-start justify-between">
<StatContainer>
<StatLabel>{_("Bank Statement Balance as per General Ledger")}</StatLabel>
<StatValue className="font-numeric">{formatCurrency(bankStatementBalanceAsPerGL, currency)}</StatValue>
</StatContainer>
<StatContainer>
<StatLabel>{_("Outstanding Checks and Deposits to clear")}</StatLabel>
<StatValue className="font-numeric">{formatCurrency(outstandingChecksDebit - outstandingChecksCredit, currency)}</StatValue>
</StatContainer>
{(incorrectlyClearedEntriesDebit > 0 || incorrectlyClearedEntriesCredit > 0) && <StatContainer>
<StatLabel className="text-ink-red-3">{_("Checks and Deposits incorrectly cleared")}</StatLabel>
<StatValue className="text-ink-red-3 font-numeric">{formatCurrency(incorrectlyClearedEntriesDebit - incorrectlyClearedEntriesCredit)}</StatValue>
{/* <div className="" divider={<StackDivider height='20px' />}>
{incorrectlyClearedEntriesDebit !== 0 && <StatHelpText>Debit: {formatCurrency(incorrectlyClearedEntriesDebit)}</StatHelpText>}
{incorrectlyClearedEntriesCredit !== 0 && <StatHelpText>Credit: {formatCurrency(incorrectlyClearedEntriesCredit)}</StatHelpText>}
</div> */}
</StatContainer>}
<StatContainer>
<StatLabel>{_("Calculated Bank Statement Balance")}</StatLabel>
<StatValue className="font-numeric">{formatCurrency(calculatedBankStatementBalance)}</StatValue>
</StatContainer>
</div>
}
export default BankReconciliationStatement

View File

@@ -0,0 +1,422 @@
import { useAtomValue, useSetAtom } from "jotai"
import { MissingFiltersBanner } from "./MissingFiltersBanner"
import { bankRecDateAtom, bankRecUnreconcileModalAtom, selectedBankAccountAtom } from "./bankRecAtoms"
import { Paragraph } from "@/components/ui/typography"
import { formatDate } from "@/lib/date"
import { ListView, type ListViewColumnMeta } from "@/components/ui/list-view"
import { formatCurrency, getCurrencyFormatInfo } from "@/lib/numbers"
import { getCompanyCurrency } from "@/lib/company"
import { ArrowDownRight, ArrowUpRight, CheckCircle2, ChevronDown, DollarSign, ExternalLink, ImportIcon, ListIcon, Search, Undo2, XCircle } from "lucide-react"
import ErrorBanner from "@/components/ui/error-banner"
import { Badge } from "@/components/ui/badge"
import { useGetBankTransactions } from "./utils"
import { BankTransaction } from "@/types/Accounts/BankTransaction"
import { Button } from "@/components/ui/button"
import _ from "@/lib/translate"
import { Input } from "@/components/ui/input"
import CurrencyInput from "react-currency-input-field"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { getCurrencySymbol } from "@/lib/currency"
import { useDebounceValue } from "usehooks-ts"
import type { ColumnDef } from "@tanstack/react-table"
import { useCallback, useMemo, useState } from "react"
import { Link } from "react-router"
import { Empty, EmptyTitle, EmptyHeader, EmptyMedia, EmptyDescription, EmptyContent } from "@/components/ui/empty"
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"
const BankTransactions = () => {
const selectedBank = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
if (!selectedBank || !dates) {
return <MissingFiltersBanner text={_("Please select a bank and set the date range")} />
}
return <>
<BankTransactionListView />
</>
}
const BankTransactionListView = () => {
const { data, error } = useGetBankTransactions()
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
const formattedFromDate = formatDate(dates.fromDate)
const formattedToDate = formatDate(dates.toDate)
const setBankRecUnreconcileModalAtom = useSetAtom(bankRecUnreconcileModalAtom)
const onUndo = useCallback(
(transaction: BankTransaction) => {
setBankRecUnreconcileModalAtom(transaction.name)
},
[setBankRecUnreconcileModalAtom],
)
const accountCurrency = useMemo(
() => bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ""),
[bankAccount?.account_currency, bankAccount?.company],
)
const transactionColumns = useMemo<ColumnDef<BankTransaction, unknown>[]>(
() => [
{
accessorKey: "date",
header: _("Date"),
size: 112,
meta: { tabularNums: true } satisfies ListViewColumnMeta,
cell: ({ row }) => formatDate(row.original.date),
},
{
accessorKey: "description",
header: _("Description"),
size: 250,
// meta: { gridWidth: "minmax(0,2fr)" } satisfies ListViewColumnMeta,
cell: ({ row }) => row.original.description,
},
{
accessorKey: "reference_number",
header: _("Reference #"),
size: 128,
cell: ({ row }) => row.original.reference_number,
},
{
accessorKey: "withdrawal",
header: _("Withdrawal"),
size: 120,
meta: { align: "right" } satisfies ListViewColumnMeta,
cell: ({ row }) => <span className="font-numeric">{formatCurrency(row.original.withdrawal, accountCurrency)}</span>,
},
{
accessorKey: "deposit",
header: _("Deposit"),
size: 120,
meta: { align: "right" } satisfies ListViewColumnMeta,
cell: ({ row }) => <span className="font-numeric">{formatCurrency(row.original.deposit, accountCurrency)}</span>,
},
{
accessorKey: "unallocated_amount",
header: _("Unallocated"),
size: 120,
meta: { align: "right" } satisfies ListViewColumnMeta,
cell: ({ row }) => <span className="font-numeric">{formatCurrency(row.original.unallocated_amount, accountCurrency)}</span>,
},
{
accessorKey: "transaction_type",
header: _("Type"),
size: 112,
cell: ({ row }) =>
row.original.transaction_type ? <Badge>{row.original.transaction_type}</Badge> : null,
},
{
id: "status",
header: _("Status"),
size: 168,
meta: { truncate: false, truncateTooltip: false } satisfies ListViewColumnMeta,
cell: ({ row }) => {
const tx = row.original
if (!tx.allocated_amount || (tx.allocated_amount && tx.allocated_amount === 0)) {
return (
<Badge theme="red">
<XCircle />
{_("Not Reconciled")}
</Badge>
)
}
if (tx.allocated_amount && tx.allocated_amount > 0 && tx.unallocated_amount !== 0) {
return (
<Badge theme="orange">
<CheckCircle2 />
{_("Partially Reconciled")}
</Badge>
)
}
return (
<Badge theme="green">
<CheckCircle2 />
{_("Reconciled")}
</Badge>
)
},
},
{
id: "actions",
header: _("Actions"),
size: 200,
enableResizing: false,
meta: { truncate: false, truncateTooltip: false } satisfies ListViewColumnMeta,
cell: ({ row }) => (
<div className="flex gap-2 ps-0.5 items-center">
<Button variant="ghost" asChild size='sm'>
<a
href={`/desk/bank-transaction/${row.original.name}`}
target="_blank"
rel="noreferrer"
// className="text-ink-gray-8 underline underline-offset-4 inline-flex gap-2"
>
{_("View")} <ExternalLink className="w-4 h-4" />
</a>
</Button>
{row.original.allocated_amount && row.original.allocated_amount > 0 ? (
<Button
variant="ghost"
onClick={() => onUndo(row.original)}
size="sm"
theme='red'
>
<Undo2 />
{_("Undo")}
</Button>
) : null}
</div>
),
},
],
[_, accountCurrency, onUndo],
)
const [search, setSearch] = useDebounceValue('', 250)
const [amountFilter, setAmountFilter] = useState<{ value: number, stringValue?: string | number }>({ value: 0, stringValue: '0.00' })
const [typeFilter, setTypeFilter] = useState('All')
const [status, setStatus] = useState<'Reconciled' | 'Unreconciled' | 'All' | 'Partially Reconciled'>('All')
const onSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value)
}
const filteredResults = useMemo(() => {
if (!data) {
return []
}
return data.message.filter((transaction) => {
if (search && !transaction.description?.toLowerCase().includes(search.toLowerCase())) {
return false
}
if (typeFilter !== 'All') {
if (typeFilter === 'Debits' && transaction.deposit && transaction.deposit > 0) {
return false
}
if (typeFilter === 'Credits' && transaction.withdrawal && transaction.withdrawal > 0) {
return false
}
}
if (status !== 'All') {
if (status === 'Reconciled' && transaction.status !== 'Reconciled') {
return false
}
if (status === 'Unreconciled') {
if (transaction.status === 'Reconciled') {
return false
}
// Filter out partially reconciled transactions
if (transaction.allocated_amount && transaction.allocated_amount > 0 && transaction.unallocated_amount !== 0) {
return false
}
}
if (status === 'Partially Reconciled') {
if (transaction.status === 'Reconciled') {
return false
}
if ((transaction.allocated_amount ?? 0) === 0) {
return false
}
}
}
if (amountFilter.value > 0 && transaction.withdrawal !== amountFilter.value && transaction.deposit !== amountFilter.value) {
return false
}
return true
})
}, [data, search, amountFilter, typeFilter, status])
return <div className="space-y-2 py-2">
<div className="flex gap-2 justify-between items-center">
<Paragraph className="text-sm">
<span dangerouslySetInnerHTML={{
__html: _("Below is a list of all bank transactions imported in the system for the bank account {0} between {1} and {2}.", [`<strong>${bankAccount?.account_name}</strong>`, `<strong>${formattedFromDate}</strong>`, `<strong>${formattedToDate}</strong>`])
}} />
</Paragraph>
<Button size='md' variant='subtle' asChild>
<Link to="/statement-importer">
<ImportIcon />
{_("Import Bank Statement")}
</Link>
</Button>
</div>
{error && <ErrorBanner error={error} />}
<Filters
onSearchChange={onSearchChange}
search={search}
results={filteredResults}
setAmountFilter={setAmountFilter}
amountFilter={amountFilter}
onTypeFilterChange={setTypeFilter}
typeFilter={typeFilter}
status={status}
setStatus={setStatus}
/>
<ListView
data={filteredResults}
columns={transactionColumns}
getRowId={(row) => row.name}
maxHeight="calc(100vh - 200px)"
scrollAreaClassName="min-h-[calc(100vh-200px)]"
emptyState={<Empty>
<EmptyMedia>
<ListIcon />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{_("No bank transactions found")}</EmptyTitle>
<EmptyDescription>{_("There are no transactions in the system for the selected bank account and dates that match the filters.")}</EmptyDescription>
</EmptyHeader>
{data && data.message.length === 0 ? <EmptyContent>
<Button type='button' asChild variant='outline'>
<Link to="/statement-importer">
{_("Import Bank Statement")}
</Link>
</Button>
</EmptyContent> : null}
</Empty>}
/>
</div>
}
interface FilterProps {
onSearchChange: (e: React.ChangeEvent<HTMLInputElement>) => void
search: string
results: BankTransaction[]
setAmountFilter: (value: { value: number, stringValue?: string | number }) => void
amountFilter: { value: number, stringValue?: string | number }
onTypeFilterChange: (type: string) => void
typeFilter: string
status: 'Reconciled' | 'Unreconciled' | 'All' | 'Partially Reconciled'
setStatus: (status: 'Reconciled' | 'Unreconciled' | 'All' | 'Partially Reconciled') => void
}
const Filters = ({
onSearchChange,
search,
results,
setAmountFilter,
amountFilter,
onTypeFilterChange,
typeFilter,
status,
setStatus,
}: FilterProps) => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const currency = bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? '')
const currencySymbol = getCurrencySymbol(currency)
const formatInfo = getCurrencyFormatInfo(currency)
const groupSeparator = formatInfo.group_sep || ","
const decimalSeparator = formatInfo.decimal_str || "."
return <div className="flex py-2 w-full gap-2">
<InputGroup variant='outline'>
<label className="sr-only">{_("Search transactions")}</label>
<InputGroupAddon>
<Search className="w-4 h-4 text-ink-gray-5" />
</InputGroupAddon>
<Input
placeholder={_("Search")} type='search' onChange={onSearchChange} variant='outline' defaultValue={search}
className="border-none px-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0" />
<InputGroupAddon align='inline-end'>
<span className="text-sm text-ink-gray-5 text-nowrap whitespace-nowrap">{results?.length} {_(results?.length === 1 ? "result" : "results")}</span>
</InputGroupAddon>
</InputGroup>
<div className="w-[25%]">
<label className="sr-only">{_("Filter by amount")}</label>
<CurrencyInput
groupSeparator={groupSeparator}
decimalSeparator={decimalSeparator}
placeholder={`${currencySymbol}0${decimalSeparator}00`}
decimalsLimit={2}
value={amountFilter.stringValue}
maxLength={12}
decimalScale={2}
prefix={currencySymbol}
onValueChange={(v, _n, values) => {
// If the input ends with a decimal or a decimal with trailing zeroes, store the string since we need the user to be able to type the decimals.
// When the user eventually types the decimals or blurs out, the value is formatted anyway.
// Otherwise store the float value
// Check if the value ends with a decimal or a decimal with trailing zeroes
const isDecimal = v?.endsWith(decimalSeparator) || v?.endsWith(decimalSeparator + '0')
const newValue = isDecimal ? v : values?.float ?? ''
setAmountFilter({
value: Number(newValue),
stringValue: newValue
})
}}
// @ts-expect-error - CurrencyInputProps doesn't have a variant prop but Input does
variant={"outline"}
customInput={Input}
/>
</div>
<div className="w-[25%]">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size='md' className="min-w-32 w-full text-start justify-between">
<div className="flex gap-2 items-center">
{typeFilter === 'All' ? <DollarSign className="w-4 h-4 text-ink-gray-5" /> : typeFilter === 'Debits' ? <ArrowUpRight className="w-4 h-4 text-ink-red-3" /> : <ArrowDownRight className="w-4 h-4 text-ink-green-3" />}
{_(typeFilter)}
</div>
<ChevronDown className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => onTypeFilterChange('All')}><DollarSign /> {_("All")}</DropdownMenuItem>
<DropdownMenuItem onClick={() => onTypeFilterChange('Debits')}><ArrowUpRight className="text-ink-red-3" /> {_("Debits")}</DropdownMenuItem>
<DropdownMenuItem onClick={() => onTypeFilterChange('Credits')}><ArrowDownRight className="text-ink-green-3" /> {_("Credits")}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="w-[25%]">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size='md' className="min-w-32 w-full text-start justify-between">
<div className="flex gap-2 items-center">
{status === 'All' ? <ListIcon className="w-4 h-4 text-ink-gray-5" /> :
status === 'Reconciled' ? <CheckCircle2 className="w-4 h-4 text-ink-green-3" /> :
status === 'Unreconciled' ? <XCircle className="w-4 h-4 text-ink-red-3" /> :
<CheckCircle2 className="w-4 h-4 text-yellow-500" />}
{_(status)}
</div>
<ChevronDown className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => setStatus('All')}>{<ListIcon className="w-4 h-4 text-ink-gray-5" />} {_("All")}</DropdownMenuItem>
<DropdownMenuItem onClick={() => setStatus('Reconciled')}>{<CheckCircle2 className="w-4 h-4 text-ink-green-3" />} {_("Reconciled")}</DropdownMenuItem>
<DropdownMenuItem onClick={() => setStatus('Unreconciled')}>{<XCircle className="w-4 h-4 text-ink-red-3" />} {_("Unreconciled")}</DropdownMenuItem>
<DropdownMenuItem onClick={() => setStatus('Partially Reconciled')}>{<CheckCircle2 className="w-4 h-4 text-yellow-500" />} {_("Partially Reconciled")}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
}
export default BankTransactions

View File

@@ -0,0 +1,125 @@
import { AlertDialog, AlertDialogOverlay, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from "@/components/ui/alert-dialog"
import { useAtom, useAtomValue } from "jotai"
import { bankRecDateAtom, bankRecUnreconcileModalAtom, selectedBankAccountAtom } from "./bankRecAtoms"
import { useMemo } from "react"
import { useFrappeGetDoc, useFrappePostCall, useSWRConfig } from "frappe-react-sdk"
import { BankTransaction } from "@/types/Accounts/BankTransaction"
import { toast } from "sonner"
import ErrorBanner from "@/components/ui/error-banner"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { formatCurrency } from "@/lib/numbers"
import { Badge } from "@/components/ui/badge"
import { slug } from "@/lib/frappe"
import SelectedTransactionDetails from "./SelectedTransactionDetails"
import _ from "@/lib/translate"
const BankTransactionUnreconcileModal = () => {
const [unreconcileModal, setBankRecUnreconcileModal] = useAtom(bankRecUnreconcileModalAtom)
const onOpenChange = (v: boolean) => {
if (!v) {
setBankRecUnreconcileModal('')
}
}
return <AlertDialog open={!!unreconcileModal} onOpenChange={onOpenChange}>
<AlertDialogOverlay />
<AlertDialogContent className="min-w-2xl">
<AlertDialogHeader>
<AlertDialogTitle>{_("Undo Transaction Reconciliation")}</AlertDialogTitle>
<AlertDialogDescription>
{_("Are you sure you want to unreconcile this transaction?")}
</AlertDialogDescription>
</AlertDialogHeader>
<BankTransactionUnreconcileModalContent />
</AlertDialogContent>
</AlertDialog>
}
const BankTransactionUnreconcileModalContent = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
const { mutate } = useSWRConfig()
const [unreconcileModal, setBankRecUnreconcileModal] = useAtom(bankRecUnreconcileModalAtom)
const { data: transaction, error } = useFrappeGetDoc<BankTransaction>('Bank Transaction', unreconcileModal)
const { call, loading, error: unreconcileError } = useFrappePostCall('erpnext.accounts.doctype.bank_transaction.bank_transaction.unreconcile_transaction')
const onUnreconcile = (event: React.MouseEvent<HTMLButtonElement>) => {
call({
transaction_name: unreconcileModal
}).then(() => {
// Mutate the transactions list, unreconciled transactions list and account closing balance
mutate(`bank-reconciliation-bank-transactions-${bankAccount?.name}-${dates.fromDate}-${dates.toDate}`)
mutate(`bank-reconciliation-unreconciled-transactions-${bankAccount?.name}-${dates.fromDate}-${dates.toDate}`)
mutate(`bank-reconciliation-account-closing-balance-${bankAccount?.name}-${dates.toDate}`)
toast.success(_("Transaction Unreconciled"))
setBankRecUnreconcileModal('')
})
event.preventDefault()
}
const vouchersWhichWillBeCancelled = useMemo(() => {
return transaction?.payment_entries?.filter((payment) => payment.reconciliation_type === 'Voucher Created')
}, [transaction])
return <div>
<div className="flex flex-col gap-3">
{error && <ErrorBanner error={error} />}
{unreconcileError && <ErrorBanner error={unreconcileError} />}
{transaction && <SelectedTransactionDetails transaction={transaction} />}
<span className="font-medium text-sm">{_("This transaction has been reconciled with the following document(s):")}</span>
<Table>
<TableHeader>
<TableRow>
<TableHead>{_("Document")}</TableHead>
<TableHead>{_("Amount")}</TableHead>
<TableHead>{_("Reconciliation Type")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{transaction?.payment_entries?.map((voucher) => {
return <TableRow key={voucher.name}>
<TableCell>
<a className="underline underline-offset-4"
target="_blank"
rel="noopener noreferrer"
href={`/desk/${slug(voucher.payment_document as string)}/${voucher.payment_entry}`}
>
{`${_(voucher.payment_document)}: ${voucher.payment_entry}`}
</a>
</TableCell>
<TableCell>{formatCurrency(voucher.allocated_amount)}</TableCell>
<TableCell>{voucher.reconciliation_type === 'Voucher Created' ?
<Badge theme="green">{_(voucher.reconciliation_type)}</Badge> :
<Badge theme="blue">{_(voucher.reconciliation_type ?? "Matched")}</Badge>}</TableCell>
</TableRow>
})}
</TableBody>
</Table>
<div className="py-4">
{vouchersWhichWillBeCancelled && vouchersWhichWillBeCancelled?.length > 0 && <span>The following documents will be <strong>cancelled</strong>:</span>}
{vouchersWhichWillBeCancelled && vouchersWhichWillBeCancelled?.length > 0 && <ol className="ms-6 list-disc [&>li]:mt-2">
{vouchersWhichWillBeCancelled?.map((voucher) => {
return <li key={voucher.name}>{_(voucher.payment_document)}: {voucher.payment_entry}</li>
})}
</ol>}
</div>
</div>
<AlertDialogFooter>
<AlertDialogCancel disabled={loading}>{_("Cancel")}</AlertDialogCancel>
<AlertDialogAction onClick={onUnreconcile} theme="red" disabled={loading}>
{_("Unreconcile")}
</AlertDialogAction>
</AlertDialogFooter>
</div>
}
export default BankTransactionUnreconcileModal

View File

@@ -0,0 +1,92 @@
import { Button } from "@/components/ui/button"
import { selectedCompanyAtom, useCurrentCompany } from "@/hooks/useCurrentCompany"
import { useSetAtom } from "jotai"
import { Building2, Check, ChevronDown } from "lucide-react"
import { useState } from "react"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { cn } from "@/lib/utils"
import _ from "@/lib/translate"
import { selectedBankAccountAtom } from "./bankRecAtoms"
const CompanySelector = ({ onChange }: { onChange?: (company: string) => void }) => {
const [open, setOpen] = useState(false)
const [searchQuery, setSearchQuery] = useState("")
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const options = window.frappe?.boot?.docs?.filter((doc: Record<string, any>) => doc.doctype === ":Company").map((company: Record<string, any>) => company.name) || []
const setSelectedCompany = useSetAtom(selectedCompanyAtom)
const setSelectedBankAccount = useSetAtom(selectedBankAccountAtom)
const selectedCompany = useCurrentCompany()
const handleSelectCompany = (company: string) => {
setSelectedCompany(company)
setSearchQuery("")
setOpen(false)
// Only reset bank account if the company is changed
if (selectedCompany !== company) {
setSelectedBankAccount(null)
onChange?.(company)
}
}
return (<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
type='button'
role="combobox"
size='md'
aria-expanded={open}
className="justify-between"
>
<div className="flex items-center gap-2">
<Building2 />
{selectedCompany}
</div>
<ChevronDown className="text-ink-gray-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="min-w-56 w-fit p-0">
<Command value={selectedCompany}>
{options.length > 5 && <CommandInput placeholder={_("Search company...")} className="h-9" />}
<CommandList>
<CommandEmpty>{_("No company found.")}</CommandEmpty>
<CommandGroup>
{options.map((option: string) => (
<CommandItem
key={option}
value={option}
onSelect={(currentValue) => {
handleSelectCompany(currentValue)
}}
>
{option}
<Check
className={cn(
"ms-auto",
searchQuery === option ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>)
}
export default CompanySelector

View File

@@ -0,0 +1,229 @@
import { useAtomValue } from "jotai"
import { MissingFiltersBanner } from "./MissingFiltersBanner"
import { bankRecDateAtom, selectedBankAccountAtom } from "./bankRecAtoms"
import { useCurrentCompany } from "@/hooks/useCurrentCompany"
import { Paragraph } from "@/components/ui/typography"
import type { ColumnDef } from "@tanstack/react-table"
import { useCallback, useMemo } from "react"
import { useFrappeGetCall, useFrappePostCall } from "frappe-react-sdk"
import { QueryReportReturnType } from "@/types/custom/Reports"
import { formatDate } from "@/lib/date"
import { ListView, type ListViewColumnMeta } from "@/components/ui/list-view"
import { formatCurrency } from "@/lib/numbers"
import { getCompanyCurrency } from "@/lib/company"
import { getErrorMessage, slug } from "@/lib/frappe"
import { Button } from "@/components/ui/button"
import { toast } from "sonner"
import { PartyPopper } from "lucide-react"
import ErrorBanner from "@/components/ui/error-banner"
import _ from "@/lib/translate"
import { Empty, EmptyTitle, EmptyDescription, EmptyMedia, EmptyHeader } from "@/components/ui/empty"
const IncorrectlyClearedEntries = () => {
const companyID = useCurrentCompany()
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
if (!companyID || !bankAccount || !dates) {
const missingFields = []
if (!companyID) {
missingFields.push('Company')
}
if (!bankAccount) {
missingFields.push('Bank Account')
}
if (!dates) {
missingFields.push('Dates')
}
return <MissingFiltersBanner text={`Please select ${missingFields.join(', ')} to view the incorrectly cleared entries.`} />
}
return <IncorrectlyClearedEntriesView />
}
interface IncorrectlyClearedEntry {
payment_document: string
payment_entry: string
debit: number
credit: number
posting_date: string,
clearance_date: string,
}
const IncorrectlyClearedEntriesView = () => {
const companyID = useCurrentCompany()
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
const filters = useMemo(() => {
return JSON.stringify({
company: companyID,
account: bankAccount?.account,
report_date: dates.toDate
})
}, [companyID, bankAccount, dates])
const { data, error, mutate } = useFrappeGetCall<{ message: QueryReportReturnType<IncorrectlyClearedEntry> }>('frappe.desk.query_report.run', {
report_name: 'Cheques and Deposits Incorrectly cleared',
filters,
ignore_prepared_report: 1,
are_default_filters: false,
}, `Report-Cheques and Deposits Incorrectly cleared-${filters}`, { keepPreviousData: true, revalidateOnFocus: false }, 'POST')
const formattedToDate = formatDate(dates.toDate)
const { call: clearClearingDate } = useFrappePostCall('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.clear_clearing_date')
const onClearClick = useCallback(
(voucher_type: string, voucher_name: string) => {
clearClearingDate({ voucher_type, voucher_name })
.then(() => {
toast.success(_("Cleared"), {
duration: 1000,
})
mutate()
})
.catch((e) => {
toast.error(_("There was an error while performing the action."), {
description: getErrorMessage(e),
duration: 5000,
})
})
},
[clearClearingDate, mutate, _],
)
const accountCurrency = useMemo(
() => bankAccount?.account_currency ?? getCompanyCurrency(companyID),
[bankAccount?.account_currency, companyID],
)
const incorrectlyClearedColumns = useMemo<ColumnDef<IncorrectlyClearedEntry, unknown>[]>(
() => [
{
accessorKey: "payment_document",
header: _("Document Type"),
size: 128,
cell: ({ row }) => _(row.original.payment_document),
},
{
id: "payment_entry",
header: _("Payment Document"),
size: 160,
meta: {
getTooltipText: (r) => {
const x = r as IncorrectlyClearedEntry
return [x.payment_document, x.payment_entry].filter(Boolean).join(" · ") || undefined
},
} satisfies ListViewColumnMeta,
cell: ({ row }) => (
<a
target="_blank"
rel="noreferrer"
className="text-ink-gray-8 block min-w-0 w-full underline underline-offset-4"
href={`/desk/${slug(row.original.payment_document)}/${row.original.payment_entry}`}
>
{row.original.payment_entry}
</a>
),
},
{
accessorKey: "debit",
header: _("Debit"),
size: 120,
meta: { align: "right" } satisfies ListViewColumnMeta,
cell: ({ row }) => formatCurrency(row.original.debit, accountCurrency),
},
{
accessorKey: "credit",
header: _("Credit"),
size: 120,
meta: { align: "right" } satisfies ListViewColumnMeta,
cell: ({ row }) => formatCurrency(row.original.credit, accountCurrency),
},
{
accessorKey: "posting_date",
header: _("Posting Date"),
size: 118,
meta: { tabularNums: true } satisfies ListViewColumnMeta,
cell: ({ row }) => formatDate(row.original.posting_date),
},
{
accessorKey: "clearance_date",
header: _("Clearance Date"),
size: 118,
meta: { tabularNums: true } satisfies ListViewColumnMeta,
cell: ({ row }) => formatDate(row.original.clearance_date),
},
{
id: "actions",
header: _("Actions"),
size: 180,
enableResizing: false,
meta: { truncate: false, truncateTooltip: false } satisfies ListViewColumnMeta,
cell: ({ row }) => (
<Button
variant="link"
size="sm"
className="text-ink-red-3 px-0"
onClick={() => onClearClick(row.original.payment_document, row.original.payment_entry)}
>
{_("Reset Clearing Date")}
</Button>
),
},
],
[_, accountCurrency, onClearClick],
)
return <div className="space-y-4 py-2">
<div>
<Paragraph className="text-sm">
<span dangerouslySetInnerHTML={{
__html: _("This report shows all entries in the system where the <strong>clearance date is before the posting date</strong> which is incorrect.")
}} />
<br />
{data && data.message.result.length > 0 && <span>
<span dangerouslySetInnerHTML={{
__html: _("Entries below have a posting date after {0} but the clearance date is before {1}.", [`<strong>${formattedToDate}</strong>`, `<strong>${formattedToDate}</strong>`])
}} />
<br />
{_("You can reset the clearing dates of these entries here.")}
</span>}
</Paragraph>
</div>
{error && <ErrorBanner error={error} />}
{data && data.message.result.length > 0 && (
<div className="space-y-2">
<p className="text-ink-gray-5 text-sm">{_("Incorrectly cleared entries as per the report.")}</p>
<ListView
data={data.message.result}
columns={incorrectlyClearedColumns}
getRowId={(row) => `${row.payment_entry}-${row.posting_date}`}
maxHeight="min(70vh, 640px)"
emptyState={_("No rows to display.")}
/>
</div>
)}
{data && data.message.result.length === 0 &&
<Empty>
<EmptyMedia>
<PartyPopper />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{_("It's all good!")}</EmptyTitle>
<EmptyDescription>{_("There are no entries in the system where the clearance date is before the posting date.")}</EmptyDescription>
</EmptyHeader>
</Empty>
}
</div>
}
export default IncorrectlyClearedEntries

View File

@@ -0,0 +1,949 @@
import { useAtom, useAtomValue, useSetAtom } from "jotai"
import { bankRecAmountFilter, bankRecDateAtom, bankRecRecordJournalEntryModalAtom, bankRecRecordPaymentModalAtom, bankRecSelectedTransactionAtom, bankRecTransactionTypeFilter, bankRecTransferModalAtom, selectedBankAccountAtom } from "./bankRecAtoms"
import { H4 } from "@/components/ui/typography"
import { useMemo, useRef } from "react"
import { getCompanyCurrency } from "@/lib/company"
import ErrorBanner from "@/components/ui/error-banner"
import { Separator } from "@/components/ui/separator"
import Fuse from 'fuse.js'
import { getSearchResults, LinkedPayment, UnreconciledTransaction, useGetRuleForTransaction, useGetUnreconciledTransactions, useGetVouchersForTransaction, useIsTransactionWithdrawal, useReconcileTransaction, useTransactionSearch } from "./utils"
import { Input } from "@/components/ui/input"
import { AlertCircleIcon, ArrowDownRight, ArrowRightIcon, ArrowRightLeft, ArrowUpRight, BadgeCheck, ChevronDown, DollarSign, Landmark, LandmarkIcon, ListIcon, Loader2, Receipt, ReceiptIcon, Search, User, XCircle, ZapIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "@/components/ui/dropdown-menu"
import { Button } from "@/components/ui/button"
import CurrencyInput from 'react-currency-input-field'
import { getCurrencySymbol } from "@/lib/currency"
import { Virtuoso } from 'react-virtuoso'
import { formatDate } from "@/lib/date"
import { Badge } from "@/components/ui/badge"
import { formatCurrency, getCurrencyFormatInfo } from "@/lib/numbers"
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "@/components/ui/tooltip"
import { Skeleton } from "@/components/ui/skeleton"
import { slug } from "@/lib/frappe"
import _ from "@/lib/translate"
import TransferModal from "./TransferModal"
import BankEntryModal from "./BankEntryModal"
import RecordPaymentModal from "./RecordPaymentModal"
import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import SelectedTransactionsTable from "./SelectedTransactionsTable"
import MatchFilters from "./MatchFilters"
import { useHotkeys } from "react-hotkeys-hook"
import { KeyboardMetaKeyIcon } from "@/components/ui/keyboard-keys"
import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { useFrappeGetCall } from "frappe-react-sdk"
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"
import { Link } from "react-router"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { InputGroup, InputGroupAddon, InputGroupText } from "@/components/ui/input-group"
const MatchAndReconcile = ({ contentHeight }: { contentHeight: number }) => {
const selectedBank = useAtomValue(selectedBankAccountAtom)
if (!selectedBank) {
return <Empty>
<EmptyMedia>
<LandmarkIcon />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{_("Select a bank account to reconcile")}</EmptyTitle>
</EmptyHeader>
</Empty>
}
return <>
<div className={`flex items-start space-x-2`} >
<div className="flex-1">
<H4 className="text-sm font-medium">{_("Unreconciled Transactions")}</H4>
<UnreconciledTransactions contentHeight={contentHeight} />
</div>
<Separator orientation="vertical" style={{ minHeight: `${contentHeight}px` }} />
<div className="flex-1 px-1">
<H4 className="text-sm font-medium">{_("Match or Create")}</H4>
<VouchersSection contentHeight={contentHeight} />
</div>
</div>
<TransferModal />
<BankEntryModal />
<RecordPaymentModal />
</>
}
const UnreconciledTransactions = ({ contentHeight }: { contentHeight: number }) => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const currency = bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? '')
const currencySymbol = getCurrencySymbol(currency)
const formatInfo = getCurrencyFormatInfo(currency)
const groupSeparator = formatInfo.group_sep || ","
const decimalSeparator = formatInfo.decimal_str || "."
const inputRef = useRef<HTMLInputElement>(null)
const { data: unreconciledTransactions, isLoading, error } = useGetUnreconciledTransactions()
const [typeFilter, setTypeFilter] = useAtom(bankRecTransactionTypeFilter)
const [amountFilter, setAmountFilter] = useAtom(bankRecAmountFilter)
const [search, setSearch] = useTransactionSearch()
const searchIndex = useMemo(() => {
if (!unreconciledTransactions) {
return null
}
return new Fuse(unreconciledTransactions.message, {
keys: ['description', 'reference_number'],
threshold: 0.5,
includeScore: true
})
}, [unreconciledTransactions])
const results = useMemo(() => {
return getSearchResults(searchIndex, search, typeFilter, amountFilter.value, unreconciledTransactions?.message)
}, [searchIndex, search, typeFilter, amountFilter.value, unreconciledTransactions?.message])
const setSelectedTransaction = useSetAtom(bankRecSelectedTransactionAtom(bankAccount?.name || ''))
const onFilterChange = () => {
setSelectedTransaction([])
}
const onSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value)
onFilterChange()
}
const onTypeFilterChange = (type: string) => {
setTypeFilter(type)
onFilterChange()
}
const onClearFilters = () => {
setSearch('')
if (inputRef.current) {
inputRef.current.value = ''
}
setTypeFilter('All')
setAmountFilter({ value: 0, stringValue: '' })
onFilterChange()
}
const hasFilters = search !== '' || typeFilter !== 'All' || amountFilter.value !== 0
if (isLoading) {
return <UnreconciledTransactionsLoadingState />
}
return <div className="space-y-1">
<div className="flex py-2 w-full gap-2">
<InputGroup variant='outline'>
<label className="sr-only">{_("Search transactions")}</label>
<InputGroupAddon>
<Search className="w-4 h-4 text-ink-gray-5" />
</InputGroupAddon>
<Input
placeholder={_("Search")}
// type='search'
variant='outline'
onChange={onSearchChange}
defaultValue={search}
ref={inputRef}
/>
<InputGroupAddon align='inline-end'>
<InputGroupText>{results?.length} {_(results?.length === 1 ? "result" : "results")}</InputGroupText>
</InputGroupAddon>
</InputGroup>
<div>
<label className="sr-only">{_("Filter by amount")}</label>
<CurrencyInput
groupSeparator={groupSeparator}
decimalSeparator={decimalSeparator}
placeholder={`${currencySymbol}0${decimalSeparator}00`}
decimalsLimit={2}
value={amountFilter.stringValue}
maxLength={12}
decimalScale={2}
prefix={currencySymbol}
onValueChange={(v, _n, values) => {
// If the input ends with a decimal or a decimal with trailing zeroes, store the string since we need the user to be able to type the decimals.
// When the user eventually types the decimals or blurs out, the value is formatted anyway.
// Otherwise store the float value
// Check if the value ends with a decimal or a decimal with trailing zeroes
const isDecimal = v?.endsWith(decimalSeparator) || v?.endsWith(decimalSeparator + '0')
const newValue = isDecimal ? v : values?.float ?? ''
const nextAmountFilter = {
value: Number(newValue),
stringValue: newValue
}
const hasAmountFilterChanged = amountFilter.value !== nextAmountFilter.value || amountFilter.stringValue !== nextAmountFilter.stringValue
setAmountFilter(nextAmountFilter)
// `onValueChange` also fires on blur; avoid clearing selected transaction unless filter value actually changed.
if (hasAmountFilterChanged) {
onFilterChange()
}
}}
// @ts-expect-error - CurrencyInputProps doesn't have a variant prop but Input does
variant={"outline"}
customInput={Input}
/>
</div>
<div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size='md' className="min-w-32 text-start">
{typeFilter === 'All' ? <DollarSign className="text-ink-gray-5" /> : typeFilter === 'Debits' ? <ArrowUpRight className="text-ink-red-3" /> : <ArrowDownRight className="text-ink-green-3" />}
{_(typeFilter)}
<ChevronDown className="text-ink-gray-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => onTypeFilterChange('All')}><DollarSign /> {_("All")}</DropdownMenuItem>
<DropdownMenuItem onClick={() => onTypeFilterChange('Debits')}><ArrowUpRight className="text-ink-red-3" /> {_("Debits")}</DropdownMenuItem>
<DropdownMenuItem onClick={() => onTypeFilterChange('Credits')}><ArrowDownRight className="text-ink-green-3" /> {_("Credits")}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{error && <ErrorBanner error={error} />}
<OlderUnreconciledTransactionsBanner />
{results.length === 0 && <NoTransactionsFoundBanner
onClearFilters={hasFilters ? onClearFilters : undefined}
text={hasFilters ? _("No transactions found for the given filters.") : _("No unreconciled transactions found")}
description={hasFilters ? _("Try adjusting your search or filter criteria.") : _("Import your bank statement to get started.")} />}
<Virtuoso
data={results}
itemContent={(_index, transaction) => (
<UnreconciledTransactionItem transaction={transaction} />
)}
style={{ minHeight: Math.max(contentHeight - 80, 400) }}
totalCount={results?.length}
/>
</div>
}
const NoTransactionsFoundBanner = ({ text, description, onClearFilters }: { text: string, description?: string, onClearFilters?: () => void }) => {
return <Empty>
<EmptyMedia>
<ListIcon />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{text}</EmptyTitle>
{description && <EmptyDescription>{description}</EmptyDescription>}
</EmptyHeader>
<EmptyContent>
{onClearFilters ? <Button type='button' size='sm' variant='subtle' onClick={onClearFilters}>Clear Filters</Button> :
<Button type='button' asChild size='sm' variant='subtle'>
<Link to="/statement-importer">
{_("Import Bank Statement")}
</Link>
</Button>}
</EmptyContent>
</Empty>
}
const UnreconciledTransactionsLoadingState = () => {
return <div className="flex flex-col gap-2 py-2">
<div className="flex items-center gap-2 pb-2">
<Skeleton className="h-9.5 w-full" />
<Skeleton className="h-9.5 min-w-36" />
<Skeleton className="h-9.5 min-w-32" />
</div>
{Array.from({ length: 6 }).map((_, index) => (
<Skeleton key={index} className="h-16 w-full" />
))}
</div>
}
const UnreconciledTransactionItem = ({ transaction }: { transaction: UnreconciledTransaction }) => {
const selectedBank = useAtomValue(selectedBankAccountAtom)
const [selectedTransaction, setSelectedTransaction] = useAtom(bankRecSelectedTransactionAtom(selectedBank?.name || ''))
const { amount, isWithdrawal } = useIsTransactionWithdrawal(transaction)
const isSelected = selectedTransaction?.some((t) => t.name === transaction.name)
const currency = transaction.currency ?? selectedBank?.account_currency ?? getCompanyCurrency(selectedBank?.company ?? '')
const handleSelectTransaction = (event: React.MouseEvent<HTMLDivElement>) => {
// If the user is pressing the shift key, add/remove the transaction from the selected transactions
if (event.shiftKey) {
setSelectedTransaction(isSelected ? selectedTransaction.filter((t) => t.name !== transaction.name) : [...selectedTransaction, transaction])
} else {
setSelectedTransaction([transaction])
}
}
return <div className="py-1">
<div className={cn("border outline rounded-md p-2 mx-0.5 cursor-pointer transition-[color,box-shadow, bg] hover:bg-surface-gray-1",
isSelected ? "bg-surface-gray-1 border-outline-gray-5 outline-outline-gray-5" : "border-outline-gray-2 outline-none"
)}
role='button'
tabIndex={0}
onClick={handleSelectTransaction}>
<div className="flex justify-between items-start w-full">
<div className="space-y-1 overflow-hidden whitespace-pre-wrap">
<div className="flex items-center gap-1">
<span className="font-medium text-sm">{formatDate(transaction.date)}</span>
{transaction.transaction_type &&
<Badge theme="blue">{transaction.transaction_type}</Badge>}
{transaction.reference_number && <Badge
title={transaction.reference_number}
className="max-w-[300px] text-ellipsis"
>
{_("Ref")}: {transaction.reference_number}</Badge>}
{transaction.matched_transaction_rule && <Badge
theme="violet"
title={_("Matched by rule")}>
<ZapIcon className="w-4 h-4" /> {transaction.matched_transaction_rule}</Badge>}
</div>
<span className="text-sm wrap-anywhere" title={transaction.description}>{transaction.description}</span>
</div>
<div className="gap-1 flex flex-col items-end min-w-36 h-full text-end">
{isWithdrawal ? <ArrowUpRight className="size-5 text-ink-red-3" /> : <ArrowDownRight className="size-5 text-ink-green-3" />}
{amount && amount > 0 && <span className="font-semibold font-numeric text-base">{formatCurrency(amount, currency)}</span>}
{amount !== transaction.unallocated_amount && <span className="text-xs leading-normal text-ink-gray-5">{formatCurrency(transaction.unallocated_amount, currency)} {_("Unallocated")}</span>}
</div>
</div>
</div>
</div>
}
const VouchersSection = ({ contentHeight }: { contentHeight: number }) => {
const selectedBank = useAtomValue(selectedBankAccountAtom)
const selectedTransactions = useAtomValue(bankRecSelectedTransactionAtom(selectedBank?.name || ''))
if (selectedTransactions.length === 0) {
return <Empty>
<EmptyMedia>
<ReceiptIcon />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{_("Select a transaction to match and reconcile with vouchers")}</EmptyTitle>
</EmptyHeader>
</Empty>
}
if (selectedTransactions.length > 1) {
return <OptionsForMultipleTransactions transactions={selectedTransactions} />
}
return <div style={{ minHeight: contentHeight }} className="mt-2">
<OptionsForSingleTransaction transaction={selectedTransactions[0]} contentHeight={contentHeight} />
</div>
}
const useKeyboardShortcuts = () => {
const setTransferModalOpen = useSetAtom(bankRecTransferModalAtom)
const setRecordPaymentModalOpen = useSetAtom(bankRecRecordPaymentModalAtom)
const setRecordJournalEntryModalOpen = useSetAtom(bankRecRecordJournalEntryModalAtom)
useHotkeys('meta+p', () => {
//
setRecordPaymentModalOpen(true)
}, {
enabled: true,
enableOnFormTags: false,
preventDefault: true
})
useHotkeys('meta+b', () => {
//
setRecordJournalEntryModalOpen(true)
}, {
enabled: true,
enableOnFormTags: false,
preventDefault: true
})
useHotkeys('meta+i', () => {
//
setTransferModalOpen(true)
}, {
enabled: true,
enableOnFormTags: false,
preventDefault: true
})
return {
setTransferModalOpen,
setRecordPaymentModalOpen,
setRecordJournalEntryModalOpen
}
}
const OptionsForMultipleTransactions = ({ transactions }: { transactions: UnreconciledTransaction[] }) => {
const { setTransferModalOpen, setRecordPaymentModalOpen, setRecordJournalEntryModalOpen } = useKeyboardShortcuts()
return <div className="flex flex-col py-4">
<Card className="gap-2">
<CardHeader>
<CardTitle>
<div className="flex items-center justify-between">
<span className="text-md font-medium">{transactions.length} {_(transactions.length === 1 ? _("transaction selected") : _("transactions selected"))}</span>
<span className="text-md font-medium font-numeric">
{formatCurrency(transactions.reduce((acc, transaction) => acc + (transaction.unallocated_amount ?? 0), 0), transactions[0].currency ?? '')}
</span>
</div>
</CardTitle>
</CardHeader>
<CardContent>
<SelectedTransactionsTable />
<CardAction className="mt-4 justify-self-center">
<div className="flex gap-3 justify-center">
<TooltipProvider>
<div className="flex gap-4 justify-center">
<Tooltip>
<TooltipTrigger asChild>
<Button
size='md'
aria-label={_("Record a bank journal entry for expenses, income or split transactions")}
onClick={() => setRecordJournalEntryModalOpen(true)}>
<Landmark /> {_("Bank Entry")}
</Button>
</TooltipTrigger>
<TooltipContent>
{_("Record a journal entry for expenses, income or split transactions")}
<KbdGroup className="ms-2">
<Kbd><KeyboardMetaKeyIcon /></Kbd>
<Kbd>B</Kbd>
</KbdGroup>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='outline'
size='md'
aria-label={_("Record a payment entry against a customer or supplier")}
onClick={() => setRecordPaymentModalOpen(true)}>
<Receipt /> {_("Record Payment")}
</Button>
</TooltipTrigger>
<TooltipContent>
{_("Record a payment entry against a customer or supplier")}
<KbdGroup className="ms-2">
<Kbd><KeyboardMetaKeyIcon /></Kbd>
<Kbd>P</Kbd>
</KbdGroup>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='outline'
size='md'
aria-label={_("Record an internal transfer to another bank/credit card/cash account")}
onClick={() => setTransferModalOpen(true)}>
<ArrowRightLeft /> {_("Transfer")}
</Button>
</TooltipTrigger>
<TooltipContent>
{_("Record an internal transfer to another bank/credit card/cash account")}
<KbdGroup className="ms-2">
<Kbd><KeyboardMetaKeyIcon /></Kbd>
<Kbd>I</Kbd>
</KbdGroup>
</TooltipContent>
</Tooltip>
</div>
</TooltipProvider>
</div>
</CardAction>
</CardContent>
</Card>
</div>
}
const OptionsForSingleTransaction = ({ transaction, contentHeight }: { transaction: UnreconciledTransaction, contentHeight: number }) => {
const { setTransferModalOpen, setRecordPaymentModalOpen, setRecordJournalEntryModalOpen } = useKeyboardShortcuts()
return <div className="flex flex-col gap-3">
<TooltipProvider>
<div className="flex items-center justify-between pt-2">
<div className="flex gap-4 justify-center">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='outline'
size='md'
aria-label={_("Record a payment entry against a customer or supplier")}
onClick={() => setRecordPaymentModalOpen(true)}>
<Receipt /> {_("Record Payment")}
</Button>
</TooltipTrigger>
<TooltipContent>
{_("Record a payment entry against a customer or supplier")}
<KbdGroup className="ms-2">
<Kbd><KeyboardMetaKeyIcon /></Kbd>
<Kbd>P</Kbd>
</KbdGroup>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='outline'
size='md'
aria-label={_("Record a bank journal entry for expenses, income or split transactions")}
onClick={() => setRecordJournalEntryModalOpen(true)}>
<Landmark /> {_("Bank Entry")}
</Button>
</TooltipTrigger>
<TooltipContent>
{_("Record a journal entry for expenses, income or split transactions")}
<KbdGroup className="ms-2">
<Kbd><KeyboardMetaKeyIcon /></Kbd>
<Kbd>B</Kbd>
</KbdGroup>
</TooltipContent>
</Tooltip>
<Tooltip >
<TooltipTrigger asChild>
<Button
variant='outline'
size='md'
aria-label={_("Record an internal transfer to another bank/credit card/cash account")}
onClick={() => setTransferModalOpen(true)}>
<ArrowRightLeft /> {_("Transfer")}
</Button>
</TooltipTrigger>
<TooltipContent>
{_("Record an internal transfer to another bank/credit card/cash account")}
<KbdGroup className="ms-2">
<Kbd><KeyboardMetaKeyIcon /></Kbd>
<Kbd>I</Kbd>
</KbdGroup>
</TooltipContent>
</Tooltip>
</div>
<MatchFilters />
</div>
</TooltipProvider>
{transaction.matched_transaction_rule && <RuleAction transaction={transaction} />}
<VouchersForTransaction transaction={transaction} contentHeight={contentHeight} />
</div>
}
const RuleAction = ({ transaction }: { transaction: UnreconciledTransaction }) => {
const { data: rule } = useGetRuleForTransaction(transaction)
const setTransferModalOpen = useSetAtom(bankRecTransferModalAtom)
const setRecordPaymentModalOpen = useSetAtom(bankRecRecordPaymentModalAtom)
const setRecordJournalEntryModalOpen = useSetAtom(bankRecRecordJournalEntryModalAtom)
if (!rule) {
return null
}
const getActionIcon = () => {
switch (rule.classify_as) {
case "Bank Entry":
return <Landmark />
case "Payment Entry":
return <Receipt className="w-6 h-6" />
case "Transfer":
return <ArrowRightLeft />
default:
return <ZapIcon />
}
}
const getActionStyles = () => {
switch (rule.classify_as) {
case "Bank Entry":
return {
border: "border-outline-blue-3",
bg: "bg-surface-blue-1/50",
text: "text-ink-blue-4",
theme: "blue",
}
case "Payment Entry":
return {
border: "border-outline-green-3",
bg: "bg-surface-green-1/50",
text: "text-ink-green-4",
theme: "green",
}
case "Transfer":
return {
border: "border-outline-violet-3",
bg: "bg-surface-violet-2/50",
text: "text-ink-violet-4",
theme: "violet",
}
default:
return {
border: "border-outline-amber-3",
bg: "bg-surface-amber-1/50",
text: "text-ink-amber-4",
theme: "orange",
}
}
}
const handleActionClick = () => {
switch (rule.classify_as) {
case "Bank Entry":
setRecordJournalEntryModalOpen(true)
break
case "Payment Entry":
setRecordPaymentModalOpen(true)
break
case "Transfer":
setTransferModalOpen(true)
break
}
}
const getActionDescription = () => {
switch (rule.classify_as) {
case "Bank Entry":
return _("Create a journal entry for expenses, income or split transactions")
case "Payment Entry":
return _("Record a payment entry against a customer or supplier")
case "Transfer":
return _("Record an internal transfer to another bank/credit card/cash account")
default:
return _("Create a new entry based on the rule")
}
}
useHotkeys('meta+r', () => {
//
handleActionClick()
}, {
enabled: true,
enableOnFormTags: false,
preventDefault: true
})
const styles = getActionStyles()
return (
<Card className={`border ${styles.border} ${styles.bg} shadow-sm hover:shadow-md transition-all duration-200`}>
<CardHeader className="pb-0">
<CardTitle className="flex justify-between items-center gap-3">
<div className="flex items-center gap-3">
<div className={`px-2.5 rounded-lg ${styles.bg} ${styles.text}`}>
{getActionIcon()}
</div>
<div className="flex flex-col gap-0.5">
<span className="font-semibold text-lg">{rule.rule_name}</span>
<span className="text-sm text-ink-gray-5 font-normal">
{rule.rule_description || _("Rule matched based on transaction description and other criteria.")}
</span>
</div>
</div>
<div className="flex items-center gap-0.5">
<Badge size='lg'
theme={rule.classify_as === "Bank Entry" ? "blue" : rule.classify_as === "Payment Entry" ? "green" : rule.classify_as === "Transfer" ? "violet" : "orange"}>
{rule.classify_as}
</Badge>
</div>
</CardTitle>
</CardHeader>
<CardContent className="pt-0 space-y-3">
<div className="flex items-center justify-between p-2 bg-surface-white rounded-lg border border-outline-gray-1">
<div className="flex items-center gap-2">
<BadgeCheck className="w-4 h-4 text-ink-green-3" />
<span className="text-sm font-medium text-ink-gray-8">{_("Recommended Action")}</span>
</div>
<Badge variant="ghost" theme={styles.theme as "blue" | "green" | "violet" | "orange"}>
{_("Priority")} {rule.priority}
</Badge>
</div>
<div className="space-y-2">
{rule.account && (
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-ink-gray-8">{_("Account")}:</span>
<span className="text-sm">{rule.account}</span>
</div>
)}
{rule.party_type && rule.party && (
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-ink-gray-8">{_("Party")}:</span>
<span className="text-sm">{rule.party} ({_(rule.party_type)})</span>
</div>
)}
</div>
<div className="pt-1">
<Button
onClick={handleActionClick}
className={`w-full`}
theme={styles.theme as "blue" | "green" | "violet"}
size="md"
>
{getActionIcon()}
<span>{_("Create")} {rule.classify_as}</span>
</Button>
<p className="text-sm text-ink-gray-5 mt-2 text-center leading-relaxed">
{getActionDescription()}
</p>
</div>
</CardContent>
</Card>
)
}
const VouchersForTransaction = ({ transaction, contentHeight }: { transaction: UnreconciledTransaction, contentHeight: number }) => {
const { data: vouchers, isLoading, error } = useGetVouchersForTransaction(transaction)
if (error) {
return <ErrorBanner error={error} />
}
if (isLoading) {
return <div className="flex flex-col gap-2">
<div className="flex items-center gap-2 text-sm text-ink-gray-5">
<Separator className="flex-1" />
<span>or</span>
<Separator className="flex-1" />
</div>
<Skeleton className="h-16 w-full" />
<Skeleton className="h-16 w-full" />
<Skeleton className="h-16 w-full" />
<Skeleton className="h-16 w-full" />
<Skeleton className="h-16 w-full" />
<Skeleton className="h-16 w-full" />
</div>
}
return <div className="relative space-y-2">
<div className="flex items-center gap-2 text-sm text-ink-gray-5">
<Separator className="flex-1" />
<span>or</span>
<Separator className="flex-1" />
</div>
{vouchers?.message.length === 0 && <Empty className="my-4">
<EmptyMedia>
<ReceiptIcon />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{_("No vouchers found for this transaction")}</EmptyTitle>
</EmptyHeader>
</Empty>}
<Virtuoso
data={vouchers?.message}
itemContent={(index, voucher) => (
<VoucherItem voucher={voucher} index={index} />
)}
style={{ height: contentHeight }}
totalCount={vouchers?.message.length}
/>
</div >
}
const VoucherItem = ({ voucher, index }: { voucher: LinkedPayment, index: number }) => {
const selectedBank = useAtomValue(selectedBankAccountAtom)
const selectedTransaction = useAtomValue(bankRecSelectedTransactionAtom(selectedBank?.name || ''))
const { amountMatches, postingDateMatches, referenceDateMatches, referenceMatchesFull, referenceMatchesPartial, isSuggested } = useMemo(() => {
const transaction = selectedTransaction?.[0]
// We need to check if the following details match:
// Amount
// Date
// Reference/Description: Full or partial
// Whether this is suggested or not - depends on the above scores
const amountMatches = voucher.paid_amount === transaction?.unallocated_amount
const postingDateMatches = voucher.posting_date === transaction?.date
const referenceDateMatches = voucher.reference_date === transaction?.date
const referenceMatchesFull = voucher.reference_no === transaction?.reference_number || voucher.reference_no === transaction?.description
const referenceMatchesPartial = transaction?.reference_number?.includes(voucher.reference_no) || transaction?.description?.includes(voucher.reference_no)
const isSuggested = amountMatches && (postingDateMatches || referenceDateMatches || referenceMatchesPartial) && index === 0
return { isSelected: false, amountMatches, postingDateMatches, referenceDateMatches, referenceMatchesFull, referenceMatchesPartial, isSuggested: isSuggested }
}, [voucher, selectedTransaction, index])
const { reconcileTransaction, loading } = useReconcileTransaction()
const onClick = () => {
if (!selectedTransaction) {
return
}
reconcileTransaction(selectedTransaction[0], voucher)
}
return <div className="py-1 px-1">
<div
className={cn("border outline overflow-hidden relative rounded-md p-2",
isSuggested ? "border-outline-green-4 bg-surface-green-1/40 outline-outline-green-4" : "border-outline-gray-2 outline-transparent"
)}
>
<div className="flex justify-between items-end gap-2">
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Badge size='md'>{_(voucher.doctype)}</Badge>
<a target="_blank"
href={`/desk/${slug(voucher.doctype)}/${voucher.name}`}
className="underline underline-offset-2 text-base"
>{voucher.name}</a>
</div>
{voucher.party && voucher.party_type && <div className="flex items-center gap-1.5 text-base">
<User size='18px' />
<span>{_(voucher.party_type)}</span>
<a target="_blank"
href={`/desk/${slug(voucher.party_type)}/${voucher.party}`}
className="underline underline-offset-2"
>{voucher.party}</a>
</div>}
<TooltipProvider>
<div className="flex items-start gap-8 py-0.5">
<div className="flex flex-col gap-1 min-w-24">
<div className="text-xs text-ink-gray-6">{_("Amount")}</div>
<div className="text-base font-medium flex items-center gap-1">{formatCurrency(voucher.paid_amount, voucher.currency)} {amountMatches ? <MatchBadge matchType="full" label={_("Amount matches the selected transaction")} /> : <MatchBadge matchType="none" label={_("Amount does not match the selected transaction")} />}</div>
</div>
<div className="flex flex-col gap-1 min-w-24">
<div className="text-xs text-ink-gray-6">{_("Posted On")}</div>
<div className="text-base font-medium flex items-center gap-1">{formatDate(voucher.posting_date)} {postingDateMatches ? <MatchBadge matchType="full" label={_("Posting date matches the selected transaction")} /> : <MatchBadge matchType="none" label={_("Posting date does not match the selected transaction")} />}</div>
</div>
{voucher.reference_date && <div className="flex flex-col gap-1 min-w-24">
<div className="text-xs text-ink-gray-6">{_("Reference Date")}</div>
<div className="text-base font-medium flex items-center gap-1">{formatDate(voucher.reference_date)} {referenceDateMatches ? <MatchBadge matchType="full" label={_("Reference date matches the selected transaction")} /> : <MatchBadge matchType="none" label={_("Reference date does not match the selected transaction")} />}</div>
</div>}
</div>
{voucher.reference_no && <div className="flex items-start gap-1">
<span className="text-p-base">
{voucher.reference_no}
&nbsp;&nbsp;
<Tooltip>
<TooltipTrigger>
<Badge theme={referenceMatchesFull ? "green" : referenceMatchesPartial ? "orange" : "red"} variant={referenceMatchesFull || referenceMatchesPartial ? "subtle" : "outline"}>
{referenceMatchesFull ? `${_("Complete Match")}` : referenceMatchesPartial ? `${_("Partial Match")}` : `${_("No Match")}`}</Badge>
</TooltipTrigger>
<TooltipContent side="top">
{referenceMatchesFull ? `${_("Reference matches the selected transaction")}` : referenceMatchesPartial ? `${_("Reference matches the selected transaction partially")}` : `${_("Reference does not match the selected transaction")}`}
</TooltipContent>
</Tooltip>
</span>
</div>}
</TooltipProvider>
</div>
<div>
<Button
variant={isSuggested || amountMatches ? "solid" : "outline"}
theme={isSuggested || amountMatches ? "green" : "gray"}
onClick={onClick} disabled={loading}>{loading ? <><Loader2 className="w-4 h-4 animate-spin" /> {_("Reconciling")}...</> : `${_("Reconcile")}`}</Button>
</div>
</div>
{isSuggested && <div className="absolute top-1.5 end-2 flex items-center gap-1 justify-center">
<Badge theme="green" variant="subtle" size='md'>{_("Suggested")}</Badge>
</div>}
</div>
</div>
}
const MatchBadge = ({ matchType, label }: { matchType: 'full' | 'partial' | 'none', label: string }) => {
return <Tooltip>
<TooltipTrigger>
{matchType === 'full' ? <BadgeCheck className="text-ink-white fill-surface-green-5 size-4" /> : matchType === 'partial' ?
<Badge theme="orange" variant="subtle">{_("Partial Match")}</Badge> :
<XCircle className="text-ink-red-4 size-4" />}
</TooltipTrigger>
<TooltipContent>
{label}
</TooltipContent>
</Tooltip>
}
const OlderUnreconciledTransactionsBanner = () => {
// A banner to show when there are unreconciled transactions for the given bank account before the current selected date
const [dates, setDates] = useAtom(bankRecDateAtom)
const selectedBank = useAtomValue(selectedBankAccountAtom)
const { data } = useFrappeGetCall<{
message: {
count: number,
oldest_date: string
}
}>("erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_older_unreconciled_transactions", {
bank_account: selectedBank?.name,
from_date: dates.fromDate,
}, undefined, {
revalidateOnFocus: false,
})
if (data && data.message.count > 0) {
return <Alert theme='gray' variant='subtle'>
<AlertCircleIcon />
<div className="flex justify-between items-center gap-1.5">
<div>
<AlertTitle> {data.message.count > 1 ? (
<span>{_("There are {0} unreconciled transactions before {1}.", [data.message.count.toString(), formatDate(dates.fromDate)])}</span>
) : (
<span>{_("There is one unreconciled transaction before {0}.", [formatDate(dates.fromDate)])}</span>
)}</AlertTitle>
<AlertDescription className="flex justify-between text-balance">
{_("The opening balance might not match your bank statement. Would you like to reconcile them?")}
</AlertDescription>
</div>
<div>
<Button
size='sm'
type='button'
theme='gray'
variant='outline'
onClick={() => setDates({ fromDate: data.message.oldest_date, toDate: dates.toDate })}>
<span>{data.message.count > 1 ? _("View older transactions") : _("View older transaction")}</span>
<ArrowRightIcon />
</Button>
</div>
</div>
</Alert>
}
return null
}
export default MatchAndReconcile

View File

@@ -0,0 +1,93 @@
import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import _ from '@/lib/translate'
import { FilterIcon } from 'lucide-react'
import { bankRecMatchFilters } from './bankRecAtoms'
import { useAtom } from 'jotai'
import { Switch } from '@/components/ui/switch'
import { Label } from '@/components/ui/label'
import { Separator } from '@/components/ui/separator'
import { useFrappeGetCall } from 'frappe-react-sdk'
import { scrub } from '@/lib/frappe'
import { useMemo } from 'react'
const MatchFilters = () => {
return (
<Popover>
<Tooltip>
<PopoverTrigger asChild>
<TooltipTrigger asChild>
<Button size='md' isIconButton variant='outline' aria-label={_("Configure match filters for vouchers")}>
<FilterIcon />
</Button>
</TooltipTrigger>
</PopoverTrigger>
<TooltipContent>
{_("Configure match filters for vouchers")}
</TooltipContent>
</Tooltip>
<PopoverContent>
<div className="flex flex-col gap-4">
<ToggleSwitch label={_("Show Only Exact Amount")} id="exact_match" />
<Separator />
<MatchFiltersContent />
<ToggleSwitch label={_("Bank Transaction")} id="bank_transaction" />
</div>
</PopoverContent>
</Popover>
)
}
const MatchFiltersContent = () => {
const { data } = useFrappeGetCall<{ message: string[] }>("erpnext.accounts.doctype.bank_transaction.bank_transaction.get_doctypes_for_bank_reconciliation", undefined,
"bank_rec_doctypes", {
revalidateOnFocus: false,
revalidateIfStale: false,
revalidateOnReconnect: false,
}
)
const doctypes = useMemo(() => {
const STANDARD_DOCTYPES = ["Payment Entry", "Journal Entry", "Purchase Invoice", "Sales Invoice"]
if (data) {
return data.message.map(doctype => ({
label: doctype,
id: scrub(doctype),
}))
} else {
return STANDARD_DOCTYPES.map(doctype => ({
label: doctype,
id: scrub(doctype),
}))
}
}, [data])
return (
<div className="flex flex-col gap-4">
{doctypes.map((doctype) => (
<ToggleSwitch key={doctype.id} label={doctype.label} id={doctype.id} />
))}
</div>
)
}
const ToggleSwitch = ({ label, id }: { label: string, id: string }) => {
const [matchFilters, setMatchFilters] = useAtom(bankRecMatchFilters)
return <div className="flex items-center space-x-2">
<Switch id={id} checked={matchFilters.includes(id)} onCheckedChange={(checked) => {
if (checked) {
setMatchFilters([...matchFilters, id])
} else {
setMatchFilters(matchFilters.filter(filter => filter !== id))
}
}} />
<Label htmlFor={id}>{label}</Label>
</div>
}
export default MatchFilters

View File

@@ -0,0 +1,10 @@
import { Paragraph } from "@/components/ui/typography"
import { cn } from "@/lib/utils"
import { ReactNode } from "react"
export const MissingFiltersBanner = ({ text, className }: { text: ReactNode, className?: string }) => {
return <div className={cn("min-h-[50vh] flex items-center justify-center", className)}>
<Paragraph>{text}</Paragraph>
</div>
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
import { Button } from "@/components/ui/button"
import ErrorBanner from "@/components/ui/error-banner"
import { Form } from "@/components/ui/form"
import { useCurrentCompany } from "@/hooks/useCurrentCompany"
import _ from "@/lib/translate"
import { BankTransactionRule } from "@/types/Accounts/BankTransactionRule"
import { useFrappeCreateDoc } from "frappe-react-sdk"
import { toast } from "sonner"
import { RuleForm } from "./RuleForm"
import { useForm } from "react-hook-form"
import { SettingsPanelHeader, SettingsPanelDescription, SettingsPanelTitle, SettingsPanelContent } from "@/components/ui/settings-dialog"
import { useHotkeys } from "react-hotkeys-hook"
type Props = {
onCreate: VoidFunction
}
const CreateNewRule = ({ onCreate }: Props) => {
const currentCompany = useCurrentCompany()
const form = useForm<BankTransactionRule>({
defaultValues: {
rule_name: "",
company: currentCompany,
rule_description: "",
transaction_type: "Any",
classify_as: 'Bank Entry',
bank_entry_type: "Single Account",
description_rules: [{
check: "Contains",
}]
}
})
const { createDoc, loading, error } = useFrappeCreateDoc<BankTransactionRule>()
const onSubmit = (data: BankTransactionRule) => {
createDoc("Bank Transaction Rule", data)
.then(() => {
toast.success(_("Rule created successfully"))
onCreate()
})
}
useHotkeys('meta+s', () => {
form.handleSubmit(onSubmit)()
}, {
enabled: true,
preventDefault: true,
enableOnFormTags: true
})
return (
<>
<SettingsPanelHeader
actions={
<div className="flex items-center gap-2">
<Button variant='outline' size='md' type='button' onClick={() => onCreate()}>{_("Cancel")}</Button>
<Button type='submit' form='rule-form' size='md' disabled={loading}>
{_("Save")}
</Button>
</div>
}
>
<SettingsPanelTitle>
{_("New Rule")}
</SettingsPanelTitle>
<SettingsPanelDescription>
{_("Create a new rule to automatically classify transactions.")}
</SettingsPanelDescription>
</SettingsPanelHeader>
<SettingsPanelContent className="px-0">
<Form {...form}>
<form id='rule-form' onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col justify-between h-full overflow-y-auto px-2">
<div className="flex flex-col gap-4">
{error && <ErrorBanner error={error} />}
<RuleForm />
</div>
</form>
</Form>
</SettingsPanelContent>
</>
)
}
export default CreateNewRule

View File

@@ -0,0 +1,101 @@
import { Button } from "@/components/ui/button"
import ErrorBanner from "@/components/ui/error-banner"
import { Form } from "@/components/ui/form"
import _ from "@/lib/translate"
import { BankTransactionRule } from "@/types/Accounts/BankTransactionRule"
import { FrappeError, useFrappeGetDoc, useFrappeUpdateDoc } from "frappe-react-sdk"
import { toast } from "sonner"
import { RuleForm } from "./RuleForm"
import { useForm } from "react-hook-form"
import { Skeleton } from "@/components/ui/skeleton"
import { SettingsPanelContent, SettingsPanelDescription, SettingsPanelHeader, SettingsPanelTitle } from "@/components/ui/settings-dialog"
import { useHotkeys } from "react-hotkeys-hook"
type Props = {
onClose: VoidFunction,
ruleID: string
}
const EditRule = ({ onClose, ruleID }: Props) => {
const { data: rule, isValidating, error, mutate } = useFrappeGetDoc<BankTransactionRule>("Bank Transaction Rule", ruleID, undefined, {
revalidateOnMount: true
})
const { updateDoc, loading, error: updateError } = useFrappeUpdateDoc<BankTransactionRule>()
const onSubmit = (data: BankTransactionRule) => {
updateDoc("Bank Transaction Rule", ruleID, data)
.then(() => {
toast.success(_("Rule updated."))
mutate()
onClose()
})
}
return <>
<SettingsPanelHeader
actions={
<div className="flex items-center gap-2">
<Button variant='outline' size='md' type='button' onClick={() => onClose()}>{_("Cancel")}</Button>
<Button type='submit' form='rule-form' size='md' disabled={isValidating || loading}>
{_("Save")}
</Button>
</div>
}
>
<SettingsPanelTitle>
{rule?.rule_name}
</SettingsPanelTitle>
<SettingsPanelDescription className="sr-only">
{_("Edit this rule")}
</SettingsPanelDescription>
</SettingsPanelHeader>
<SettingsPanelContent className="px-0">
{isValidating && <div className="px-4 flex flex-col gap-4 h-full">
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
</div>}
{error && <div className="px-4 flex flex-col gap-4 h-full">
<ErrorBanner error={error} />
</div>}
{rule && <EditRuleForm rule={rule} onSubmit={onSubmit} error={updateError} />}
</SettingsPanelContent>
</>
}
const EditRuleForm = ({ rule, onSubmit, error }: { rule: BankTransactionRule, onSubmit: (data: BankTransactionRule) => void, error?: FrappeError | null }) => {
const form = useForm<BankTransactionRule>({
defaultValues: {
...rule,
}
})
useHotkeys('meta+s', () => {
form.handleSubmit(onSubmit)()
}, {
enabled: true,
preventDefault: true,
enableOnFormTags: true
})
return (
<Form {...form}>
<form id='rule-form' onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col justify-between h-full overflow-y-auto px-2">
<div className="flex flex-col gap-4">
{error && <ErrorBanner error={error} />}
<RuleForm isEdit />
</div>
</form>
</Form>
)
}
export default EditRule

View File

@@ -0,0 +1,799 @@
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Dialog, DialogTitle, DialogContent, DialogHeader, DialogDescription } from "@/components/ui/dialog"
import { FormField, FormItem, FormLabel, FormControl } from "@/components/ui/form"
import { AccountFormField, CurrencyFormField, DataField, LinkFormField, PartyTypeFormField, SelectFormField, SmallTextField } from "@/components/ui/form-elements"
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { SelectItem } from "@/components/ui/select"
import { Separator } from "@/components/ui/separator"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { H4, Paragraph } from "@/components/ui/typography"
import { today } from "@/lib/date"
import _ from "@/lib/translate"
import { cn } from "@/lib/utils"
import { BankTransactionRule } from "@/types/Accounts/BankTransactionRule"
import { BankTransactionRuleAccounts } from "@/types/Accounts/BankTransactionRuleAccounts"
import { FrappeConfig, FrappeContext } from "frappe-react-sdk"
import { ArrowDownRight, ArrowDownUp, ArrowRightLeftIcon, ArrowUpRight, LandmarkIcon, Plus, PlusCircleIcon, ReceiptIcon, Settings, Trash2 } from "lucide-react"
import { ChangeEvent, useCallback, useContext, useMemo, useRef, useState } from "react"
import { useFieldArray, useFormContext, useWatch } from "react-hook-form"
export const RuleForm = ({ isEdit = false }: { isEdit?: boolean }) => {
return <div className="flex flex-col gap-4">
<DataField
name='rule_name'
label={_("Rule Name")}
disabled={isEdit}
isRequired
inputProps={{
maxLength: 140,
disabled: isEdit,
placeholder: _("Bank Charges, Salary, etc."),
autoFocus: true,
className: "dark:disabled:bg-surface-gray-2"
}}
rules={{
required: _("Rule name is required")
}}
/>
<CompanySelector />
<SmallTextField
name='rule_description'
label={_("Rule Description")}
inputProps={{
placeholder: _("Any debit transaction with the keyword 'Bank Fee'.")
}}
/>
<TransactionTypeSelector />
<div className="grid grid-cols-2 gap-2 pt-1">
<CurrencyFormField
name='min_amount'
label={_("Minimum Amount")}
/>
<CurrencyFormField
name='max_amount'
label={_("Maximum Amount")}
/>
</div>
<DescriptionRules />
<Separator />
<RuleAction />
</div>
}
const CompanySelector = () => {
const { setValue } = useFormContext<BankTransactionRule>()
return <LinkFormField
name='company'
label={_("Company")}
doctype="Company"
isRequired
rules={{
required: _("Company is required"),
onChange: () => {
setValue('account', '')
}
}}
/>
}
/** Component to render a radio group as a toggle group with options for All, Withdrawal, Deposit */
const TransactionTypeSelector = () => {
const { control } = useFormContext<BankTransactionRule>()
return (
<FormField
control={control}
name='transaction_type'
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel className="text-sm font-medium">
{_("Transaction Type")}<span className="text-ink-red-3">*</span>
</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="grid grid-cols-3 gap-2 w-full"
>
<FormItem className="flex items-center">
<FormControl>
<RadioGroupItem
value="Any"
className="peer sr-only hidden"
/>
</FormControl>
<FormLabel
className={cn(
"w-full flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-md border cursor-pointer transition-all hover:bg-surface-gray-1 hover:text-ink-gray-8",
"peer-data-[state=checked]:bg-surface-gray-7 peer-data-[state=checked]:text-ink-white peer-data-[state=checked]:border-outline-gray-5 peer-data-[state=checked]:hover:bg-surface-gray-7 peer-data-[state=checked]:hover:text-ink-white"
)}
>
<ArrowDownUp className="w-5 h-5" />
{_("All")}
</FormLabel>
</FormItem>
<FormItem className="flex items-center">
<FormControl>
<RadioGroupItem
value="Withdrawal"
className="peer sr-only hidden"
/>
</FormControl>
<FormLabel
className={cn(
"w-full flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-md border cursor-pointer transition-all hover:bg-surface-gray-1 hover:text-ink-gray-8",
"peer-data-[state=checked]:bg-surface-red-5 peer-data-[state=checked]:text-white peer-data-[state=checked]:border-bg-surface-red-5 peer-data-[state=checked]:hover:bg-surface-red-5 peer-data-[state=checked]:hover:text-white"
)}
>
<ArrowUpRight className="w-5 h-5 peer-data-[state=checked]:text-ink-red-3" />
{_("Withdrawal")}
</FormLabel>
</FormItem>
<FormItem className="flex items-center">
<FormControl>
<RadioGroupItem
value="Deposit"
className="peer sr-only hidden"
/>
</FormControl>
<FormLabel
className={cn(
"w-full flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-md border cursor-pointer transition-all hover:bg-surface-gray-1 hover:text-ink-gray-8",
"peer-data-[state=checked]:bg-surface-green-5 peer-data-[state=checked]:text-white peer-data-[state=checked]:border-surface-green-5 peer-data-[state=checked]:hover:bg-surface-green-5 peer-data-[state=checked]:hover:text-white"
)}
>
<ArrowDownRight className="w-5 h-5 peer-data-[state=checked]:text-white" />
{_("Deposit")}
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
</FormItem>
)}
/>
)
}
const DescriptionRules = () => {
const { control } = useFormContext<BankTransactionRule>()
const { fields, append, remove } = useFieldArray({
control,
name: "description_rules"
})
const addRow = () => {
// @ts-expect-error - we don't need all fields here
append({ check: "Contains" })
}
return (
<div className="flex flex-col gap-2 pt-1">
<span className="text-sm font-medium">{_("Rules to match against the transaction description")} <span className="text-ink-red-3">*</span></span>
{fields.map((field, index) => (
<div key={field.id} className="flex w-full items-center gap-2">
<div className="min-w-36">
<SelectFormField
label={_("Type of check")}
hideLabel
name={`description_rules.${index}.check`}
rules={{
required: _("This is required")
}}>
<SelectItem value="Contains">{_("Contains")}</SelectItem>
<SelectItem value="Starts With">{_("Starts with")}</SelectItem>
<SelectItem value="Ends With">{_("Ends with")}</SelectItem>
<SelectItem value="Regex">{_("Regex")}</SelectItem>
</SelectFormField>
</div>
<div className="w-full">
<DataField
name={`description_rules.${index}.value`}
label={_("Value")}
hideLabel
inputProps={{
placeholder: _("Bank Fee, Salary, etc."),
}}
/>
</div>
<div>
<Button variant="ghost" theme='red' type='button' isIconButton onClick={() => remove(index)} disabled={fields.length === 1}>
<Trash2 />
</Button>
</div>
</div>
))}
<div>
<Button variant="outline" type='button' onClick={addRow}>
<PlusCircleIcon />
{_("Add Rule")}
</Button>
</div>
</div>
)
}
const RuleAction = () => {
const { control } = useFormContext<BankTransactionRule>()
const classify_as = useWatch({ control, name: "classify_as" })
const party_type = useWatch({ control, name: "party_type" })
const bank_entry_type = useWatch({ control, name: "bank_entry_type" })
const accountType = useMemo(() => {
if (classify_as === "Payment Entry") {
return party_type === "Supplier" ? ["Payable"] : ["Receivable"]
}
if (classify_as === "Transfer") {
return ["Bank", "Cash", "Temporary"]
}
return undefined
}, [classify_as, party_type])
return (
<div className="flex flex-col gap-4">
<H4 className="text-base text-ink-gray-7">{_("If rule matches, then:")}</H4>
<SelectFormField
name='classify_as'
isRequired
label={_("Suggest creating a")}
formDescription={_("This will just suggest creating a new entry, and will not automatically create it.")}
rules={{
required: _("This is required")
}}
>
<SelectItem value="Bank Entry"><LandmarkIcon /> {_("Bank Entry")}</SelectItem>
<SelectItem value="Payment Entry"><ReceiptIcon /> {_("Payment Entry")}</SelectItem>
<SelectItem value="Transfer"><ArrowRightLeftIcon /> {_("Transfer")}</SelectItem>
</SelectFormField>
{classify_as === "Bank Entry" && (<SelectFormField
name='bank_entry_type'
isRequired
label={_("Create Bank Entry against")}
rules={{
required: _("This is required")
}}
>
<SelectItem value="Single Account">{_("Single Account")}</SelectItem>
<SelectItem value="Multiple Accounts">{_("Multiple Accounts (Journal Template)")}</SelectItem>
</SelectFormField>)}
{classify_as === "Payment Entry" && (
<div className='grid grid-cols-4 gap-4'>
<div className="col-span-1">
<PartyTypeFormField
name='party_type'
label={_("Party Type")}
isRequired
inputProps={{
triggerProps: {
className: 'w-full'
},
}}
rules={{
required: "Party Type is required"
}}
/>
</div>
<div className="col-span-3">
<PartyField />
</div>
</div>
)}
{(((bank_entry_type === "Single Account" || !bank_entry_type) && classify_as === "Bank Entry") || classify_as !== "Bank Entry") && (<AccountFormField
name='account'
label={_("Account")}
isRequired
rules={{
required: _("Account is required")
}}
account_type={accountType}
/>)}
{bank_entry_type === "Multiple Accounts" && classify_as === "Bank Entry" && <MultipleAccountsSelection />}
</div>
)
}
const PartyField = () => {
const { control, setValue } = useFormContext<BankTransactionRule>()
const party_type = useWatch({
control,
name: `party_type`
})
const { call } = useContext(FrappeContext) as FrappeConfig
const company = useWatch({ control, name: 'company' })
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
// Fetch the party and account
if (event.target.value) {
call.get('erpnext.accounts.doctype.payment_entry.payment_entry.get_party_details', {
company: company,
party_type: party_type,
party: event.target.value,
date: today()
}).then((res) => {
setValue('account', res.message.party_account)
})
} else {
// Clear the account
setValue('account', '')
}
}
if (!party_type) {
return <DataField
name={`party`}
label={_("Party")}
isRequired
inputProps={{
disabled: true,
}}
/>
}
return <LinkFormField
name={`party`}
label={_("Party")}
rules={{
onChange
}}
doctype={party_type}
/>
}
const MultipleAccountsSelection = () => {
const { control } = useFormContext<BankTransactionRule>()
const accounts = useWatch({
control,
name: 'accounts'
}) ?? []
const [isConfigureAccountsModalOpen, setIsConfigureAccountsModalOpen] = useState(false)
return <div className="flex flex-col gap-2">
<div className="flex justify-between gap-2">
<Label>{_("Journal Template Accounts")}<span className="text-ink-red-3">*</span></Label>
<Button variant="outline" type="button" onClick={() => setIsConfigureAccountsModalOpen(true)}><Settings /> {_("Configure Accounts")}</Button>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead>{_("Account")}</TableHead>
<TableHead className="text-end">{_("Debit")}</TableHead>
<TableHead className="text-end">{_("Credit")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{accounts.length === 0 && (
<TableRow>
<TableCell colSpan={3} className="text-center">
<div className="py-2 flex flex-col gap-2 items-center">
<span>{_("No accounts configured")}</span>
<Button variant="subtle" type="button" onClick={() => setIsConfigureAccountsModalOpen(true)}>{_("Configure Accounts")}</Button>
</div>
</TableCell>
</TableRow>
)}
{accounts.map((account, index) => (
<TableRow key={index}>
<TableCell>{account.account}</TableCell>
{index === accounts.length - 1 ? <TableCell className="text-end bg-surface-gray-1" colSpan={2}>
<Tooltip>
<TooltipTrigger asChild>
<span className="text-ink-gray-5">{_("This is auto computed to balance the journal entry.")}</span>
</TooltipTrigger>
<TooltipContent>
{_("Based on the above entries, the balance amount (debit or credit) will be set for the last row to balance the journal entry.")}
</TooltipContent>
</Tooltip>
</TableCell> : <>
<TableCell className="font-numeric text-end"><AmountFormulaRenderer value={account.debit} /></TableCell>
<TableCell className="font-numeric text-end"><AmountFormulaRenderer value={account.credit} /></TableCell>
</>}
</TableRow>
))}
</TableBody>
</Table>
<ConfigureAccountsModal open={isConfigureAccountsModalOpen} onClose={() => setIsConfigureAccountsModalOpen(false)} />
</div>
}
const AmountFormulaRenderer = ({ value }: { value?: string }) => {
// If it's a string and cannot be a number, then show it as a formula
if (isNaN(Number(value))) {
let calculatedValue = "";
try {
calculatedValue = window.eval(`const transaction_amount = 200; ${value}`);
} catch (error: unknown) {
console.error(error);
calculatedValue = "Error";
}
const isComputationValid = !isNaN(Number(calculatedValue)) && calculatedValue !== undefined && calculatedValue !== null;
return <Tooltip>
<TooltipTrigger asChild>
<span className={cn("font-numeric text-end tabular-nums underline underline-offset-4", isComputationValid ? "" : "text-ink-red-3")}>{value}</span>
</TooltipTrigger>
<TooltipContent className={isComputationValid ? "" : "bg-surface-red-5"} arrowClassName={isComputationValid ? "" : "bg-surface-red-5 fill-surface-red-5"}>
<p className="text-sm">
{isComputationValid ? _("This is a formula based value.") : _("This is not a valid formula. Check the variable used in the formula.")}
<br /><br />
{_("Example: If the transaction amount is 200, then this will be calculated as {} = {}", [value ?? "", calculatedValue])}
</p>
</TooltipContent>
</Tooltip>
}
return <span className="font-numeric text-end tabular-nums">{value}</span>
}
const ConfigureAccountsModal = ({ open, onClose }: { open: boolean, onClose: () => void }) => {
return <Dialog
open={open}
onOpenChange={onClose}
>
<DialogContent className='min-w-[95vw]'>
<ConfigureAccountsModalContent />
</DialogContent>
</Dialog>
}
const ConfigureAccountsModalContent = () => {
const { control, getValues, setValue } = useFormContext<BankTransactionRule>()
const { call } = useContext(FrappeContext) as FrappeConfig
// const costCenterMapRef = useRef<Record<string, string>>({})
const partyMapRef = useRef<Record<string, string>>({})
const onPartyChange = (value: string, index: number) => {
// Get the account for the party type
if (value) {
if (partyMapRef.current[value]) {
setValue(`accounts.${index}.account`, partyMapRef.current[value])
} else {
call.get('erpnext.accounts.party.get_party_account', {
party: value,
party_type: getValues(`accounts.${index}.party_type`),
company: company
}).then((result: { message: string }) => {
setValue(`accounts.${index}.account`, result.message)
partyMapRef.current[value] = result.message
})
}
} else {
setValue(`accounts.${index}.account`, '')
}
}
const transaction_type = useWatch({
name: 'transaction_type',
control,
})
const { fields, append, remove } = useFieldArray({
control,
name: 'accounts'
})
const [selectedRows, setSelectedRows] = useState<number[]>([])
const onSelectRow = useCallback((index: number) => {
setSelectedRows(prev => {
if (prev.includes(index)) {
return prev.filter(i => i !== index)
}
return [...prev, index]
})
}, [])
const onSelectAll = useCallback(() => {
setSelectedRows(prev => {
if (prev.length === fields.length) {
return []
}
return [...fields.map((_, index) => index)]
})
}, [fields])
const onAdd = () => {
append({
party_type: '',
party: '',
account: '',
debit: '',
credit: '',
user_remark: ''
} as BankTransactionRuleAccounts, {
focusName: `accounts.${fields.length}.account`
})
}
const onRemove = useCallback(() => {
remove(selectedRows)
setSelectedRows([])
}, [remove, selectedRows])
const isWithdrawal = transaction_type === 'Withdrawal'
const company = useWatch({
name: 'company',
control,
})
return <>
<DialogHeader>
<DialogTitle>{_("Configure Accounts for Bank Entry")}</DialogTitle>
<DialogDescription>{_("Add all accounts that you want to split the transaction into.")}</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-2">
<Table>
<TableHeader>
<TableRow>
<TableHead><Checkbox
disabled={fields.length === 0}
// Make this accessible to screen readers
aria-label={_("Select all")}
checked={selectedRows.length > 0 && selectedRows.length === fields.length}
onCheckedChange={onSelectAll} /></TableHead>
<TableHead>{_("Party")}</TableHead>
<TableHead>{_("Account")} <span className="text-ink-red-3">*</span></TableHead>
{/* <TableHead>{_("Cost Center")}</TableHead> */}
<TableHead>{_("Remarks")}</TableHead>
<TableHead className="text-end">{_("Debit")}</TableHead>
<TableHead className="text-end">{_("Credit")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow className="bg-surface-gray-1 cursor-not-allowed" title={_("This is the row for the bank account. It will be auto populated based on the bank transaction.")}>
<TableCell>
<Checkbox disabled />
</TableCell>
<TableCell className="align-top">
</TableCell>
<TableCell className="align-top text-ink-gray-5">
<span className="px-2">
Bank GL Account
</span>
</TableCell>
<TableCell className="align-top">
</TableCell>
<TableCell className={"align-top text-end"}>
<span className="text-ink-gray-5 text-sm">
{transaction_type === "Withdrawal" || transaction_type === "Any" ? _("Will be auto-populated") : ""}
</span>
</TableCell>
<TableCell className={"text-end align-top"}>
<span className="text-ink-gray-5 text-sm">
{transaction_type === "Deposit" || transaction_type === "Any" ? _("Will be auto-populated") : ""}
</span>
</TableCell>
</TableRow>
{fields.map((field, index) => (
<TableRow key={field.id}>
<TableCell>
<Checkbox
checked={selectedRows.includes(index)}
onCheckedChange={() => onSelectRow(index)}
// Make this accessible to screen readers
aria-label={_("Select row {0}", [String(index + 1)])}
/>
</TableCell>
<TableCell className="align-top">
<div className="flex">
<PartyTypeFormField
name={`accounts.${index}.party_type`}
label={_("Party Type")}
isRequired
hideLabel
inputProps={{
type: isWithdrawal ? 'Payable' : 'Receivable',
triggerProps: {
className: 'rounded-e-none',
tabIndex: -1
},
}} />
<PartyRowField index={index} onChange={onPartyChange} />
</div>
</TableCell>
<TableCell className="align-top">
<AccountFormField
name={`accounts.${index}.account`}
label={_("Account")}
rules={{
required: _("Account is required"),
// onChange: (event) => {
// onAccountChange(event.target.value, index)
// }
}}
buttonClassName="min-w-64"
isRequired
hideLabel
/>
</TableCell>
{/* <TableCell className="align-top">
<LinkFormField
doctype="Cost Center"
name={`accounts.${index}.cost_center`}
label={_("Cost Center")}
filters={[["company", "=", company], ["is_group", "=", 0], ["disabled", "=", 0]]}
buttonClassName="min-w-48"
readOnly={index === 0}
hideLabel
/>
</TableCell> */}
<TableCell className="align-top">
<DataField
name={`accounts.${index}.user_remark`}
label={_("Remarks")}
inputProps={{
placeholder: _("e.g. Bank Charges"),
className: 'min-w-64',
}}
hideLabel
/>
</TableCell>
<TableCell
className={cn("text-end align-top", index === fields.length - 1 ? "cursor-not-allowed" : "")}
title={index === fields.length - 1 ? _("This is the last row. It will be auto populated based on the bank transaction.") : ""}>
<DataField
name={`accounts.${index}.debit`}
label={_("Debit")}
disabled={index === fields.length - 1}
inputProps={{
className: 'text-end',
placeholder: _("0.00"),
disabled: index === fields.length - 1
}}
hideLabel
/>
</TableCell>
<TableCell
className={cn("text-end align-top", index === fields.length - 1 ? "cursor-not-allowed" : "")}
title={index === fields.length - 1 ? _("This is the last row. It will be auto populated based on the bank transaction.") : ""}>
<DataField
name={`accounts.${index}.credit`}
label={_("Credit")}
disabled={index === fields.length - 1}
inputProps={{
className: 'text-end',
placeholder: _("0.00"),
disabled: index === fields.length - 1
}}
hideLabel
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<div className="flex justify-between gap-2">
<div className="flex gap-2 justify-end">
<div>
<Button size='sm' type='button' variant={'outline'} onClick={onAdd}><Plus /> {_("Add Row")}</Button>
</div>
{selectedRows.length > 0 && <div>
<Button size='sm' type='button' theme="red" onClick={onRemove}><Trash2 /> {_("Remove")}</Button>
</div>}
</div>
</div>
<div className="py-4">
<Separator />
</div>
<div className="flex flex-col gap-2">
<H4 className="text-base text-ink-gray-7">{_("Help")}</H4>
<Paragraph className="text-p-sm">{(_("You can set up the rule to split the transaction across multiple accounts."))}
<br />{_("You can also add credit or debit values to pre-fill - these support both static values (like 200) or formulas (like transaction_amount * 0.25).")}
<br />
<br />
<span className="font-medium">{_("Example")}:</span>
<br />
<span className="font-numeric text-sm">
transaction_amount * 0.25
</span>
<br />
<span>
{_("In this case, the amount will be calculated as 25% of the transaction amount. If the transaction amount is 200, then this will be calculated as 200 * 0.25 = 50.")}
</span>
</Paragraph>
</div>
</div>
</>
}
const PartyRowField = ({ index, onChange }: { index: number, onChange: (value: string, index: number) => void }) => {
const { control } = useFormContext<BankTransactionRule>()
const party_type = useWatch({
control,
name: `accounts.${index}.party_type`
})
if (!party_type) {
return <DataField
name={`accounts.${index}.party`}
label={_("Party")}
isRequired
inputProps={{
disabled: true,
className: 'rounded-s-none border-s-0 min-w-64'
}}
hideLabel
/>
}
return <LinkFormField
name={`accounts.${index}.party`}
label={_("Party")}
rules={{
onChange: (event) => {
onChange(event.target.value, index)
},
}}
hideLabel
buttonClassName="rounded-s-none border-s-0 min-w-64"
doctype={party_type}
/>
}

View File

@@ -0,0 +1,73 @@
import { useMemo } from 'react'
import { ArrowDownRight, ArrowUpRight, Calendar } from 'lucide-react'
import { formatCurrency } from '@/lib/numbers'
import { formatDate } from '@/lib/date'
import { UnreconciledTransaction, useGetBankAccounts } from './utils'
import { getCompanyCurrency } from '@/lib/company'
import { Card, CardContent } from '@/components/ui/card'
import { cn } from '@/lib/utils'
import _ from '@/lib/translate'
import BankLogo from '@/components/common/BankLogo'
type Props = {
transaction: UnreconciledTransaction,
showAccount?: boolean,
account?: string
}
const SelectedTransactionDetails = ({ transaction, showAccount = false, account }: Props) => {
const isWithdrawal = transaction.withdrawal && transaction.withdrawal > 0
const { banks } = useGetBankAccounts()
const bank = useMemo(() => {
if (transaction.bank_account) {
return banks?.find((bank) => bank.name === transaction.bank_account)
}
return null
}, [transaction.bank_account, banks])
const amount = transaction.withdrawal ? transaction.withdrawal : transaction.deposit
const currency = transaction.currency || getCompanyCurrency(transaction.company ?? '')
return (
<Card className='py-4'>
<CardContent className='px-4'>
<div className='flex flex-col gap-2'>
<div className='flex justify-between'>
<div className='flex flex-col gap-2'>
<div className='flex flex-col gap-1'>
<BankLogo bank={bank} iconSize='30px' imageClassName='h-10 max-w-20' />
<span className='font-medium text-sm'>{transaction.bank_account}</span>
</div>
<div className='flex items-center gap-1'>
<Calendar size='16px' />
<span className='text-sm'>{formatDate(transaction.date, 'Do MMM YYYY')}</span>
</div>
</div>
<div className='flex flex-col gap-1'>
<div className={cn('flex items-center gap-1 text-end px-0 justify-end py-1 rounded-sm',
isWithdrawal ? 'text-ink-red-3' : 'text-ink-green-3'
)}>
{isWithdrawal ? <ArrowUpRight className="w-5 h-5 text-ink-red-3" /> : <ArrowDownRight className="w-5 h-5 text-ink-green-3" />}
<span className='text-sm font-semibold uppercase'>{isWithdrawal ? _('Spent') : _('Received')}</span>
</div>
<span className='font-semibold font-numeric text-lg text-end pe-0.5'>{formatCurrency(amount, currency)}</span>
{transaction.unallocated_amount && transaction.unallocated_amount !== amount ? <span className='text-ink-gray-5'>{_("Unallocated")}: {formatCurrency(transaction.unallocated_amount)}</span> : null}
</div>
</div>
<div className='flex flex-col gap-1'>
<span className='text-sm'>{transaction.description}</span>
{transaction.reference_number ? <span className='text-sm text-ink-gray-5'>{_("Ref")}: {transaction.reference_number}</span> : null}
{showAccount && account ? <span className='text-sm text-ink-gray-5'>{_("GL Account")}: {account}</span> : null}
</div>
</div>
</CardContent >
</Card >
)
}
export default SelectedTransactionDetails

View File

@@ -0,0 +1,47 @@
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import _ from '@/lib/translate'
import { useAtomValue } from 'jotai'
import { bankRecSelectedTransactionAtom, selectedBankAccountAtom } from './bankRecAtoms'
import { formatDate } from '@/lib/date'
import { formatCurrency } from '@/lib/numbers'
import { ArrowDownRight, ArrowUpRight } from 'lucide-react'
const SelectedTransactionsTable = () => {
const selectedBankAccount = useAtomValue(selectedBankAccountAtom)
const transactions = useAtomValue(bankRecSelectedTransactionAtom(selectedBankAccount?.name ?? ''))
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>
{_("Date")}
</TableHead>
<TableHead>
{_("Description")}
</TableHead>
<TableHead className="text-end">
{_("Amount")}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{transactions.map((transaction) => (
<TableRow key={transaction.name}>
<TableCell>{formatDate(transaction.date)}</TableCell>
<TableCell className="max-w-96 text-ellipsis overflow-hidden" title={transaction.description}>{transaction.description}</TableCell>
<TableCell className="text-end flex items-center justify-end gap-1">
{transaction.withdrawal && transaction.withdrawal > 0 ? <ArrowUpRight className="w-4 h-4 text-ink-red-3" /> : <ArrowDownRight className="w-4 h-4 text-ink-green-3" />}
<span className="font-numeric font-medium">
{formatCurrency(transaction.unallocated_amount, transaction.currency ?? '')}
</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}
export default SelectedTransactionsTable

View File

@@ -0,0 +1,555 @@
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { bankRecSelectedTransactionAtom, bankRecTransferModalAtom, bankRecUnreconcileModalAtom, SelectedBank, selectedBankAccountAtom } from './bankRecAtoms'
import { Dialog, DialogContent, DialogHeader, DialogFooter, DialogClose, DialogTitle, DialogDescription } from '@/components/ui/dialog'
import _ from '@/lib/translate'
import { UnreconciledTransaction, useGetBankAccounts, useGetRuleForTransaction, useRefreshUnreconciledTransactions, useUpdateActionLog } from './utils'
import { Button } from '@/components/ui/button'
import SelectedTransactionDetails from './SelectedTransactionDetails'
import { PaymentEntry } from '@/types/Accounts/PaymentEntry'
import { useForm, useFormContext, useWatch } from 'react-hook-form'
import { FrappeConfig, FrappeContext, useFrappeGetCall, useFrappePostCall } from 'frappe-react-sdk'
import { toast } from 'sonner'
import ErrorBanner from '@/components/ui/error-banner'
import { H4 } from '@/components/ui/typography'
import { cn } from '@/lib/utils'
import { ArrowRight, Banknote, BadgeCheck, Calendar, ArrowUpRight, ArrowDownRight, CheckIcon, CheckCircle, ArrowLeft } from 'lucide-react'
import { Separator } from '@/components/ui/separator'
import { Form } from '@/components/ui/form'
import { AccountFormField, DataField, DateField, SmallTextField } from '@/components/ui/form-elements'
import SelectedTransactionsTable from './SelectedTransactionsTable'
import { useCurrentCompany } from '@/hooks/useCurrentCompany'
import { formatDate } from '@/lib/date'
import { useContext, useMemo, useState } from 'react'
import { formatCurrency } from '@/lib/numbers'
import { Label } from '@/components/ui/label'
import { FileDropzone } from '@/components/ui/file-dropzone'
import FileUploadBanner from '@/components/common/FileUploadBanner'
import { BankTransaction } from '@/types/Accounts/BankTransaction'
import { useHotkeys } from 'react-hotkeys-hook'
import { useDirection } from '@/components/ui/direction'
import BankLogo from '@/components/common/BankLogo'
const TransferModal = () => {
const [isOpen, setIsOpen] = useAtom(bankRecTransferModalAtom)
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className='min-w-7xl'>
<DialogHeader>
<DialogTitle>{_("Transfer")}</DialogTitle>
<DialogDescription>
{_("Record an internal transfer to another bank/credit card/cash account.")}
</DialogDescription>
</DialogHeader>
<TransferModalContent />
</DialogContent>
</Dialog>
)
}
const TransferModalContent = () => {
const selectedBankAccount = useAtomValue(selectedBankAccountAtom)
const selectedTransaction = useAtomValue(bankRecSelectedTransactionAtom(selectedBankAccount?.name ?? ''))
if (!selectedTransaction || !selectedBankAccount || selectedTransaction.length === 0) {
return <div className='p-4'>
<span className='text-center'>{_("No transaction selected")}</span>
</div>
}
if (selectedTransaction.length === 1) {
return <InternalTransferForm
selectedBankAccount={selectedBankAccount}
selectedTransaction={selectedTransaction[0]} />
}
return <BulkInternalTransferForm transactions={selectedTransaction} />
}
const BulkInternalTransferForm = ({ transactions }: { transactions: UnreconciledTransaction[] }) => {
const form = useForm<{
bank_account: string
}>()
const setIsOpen = useSetAtom(bankRecTransferModalAtom)
const { call: createPaymentEntry, loading, error } = useFrappePostCall<{ message: { transaction: BankTransaction, payment_entry: PaymentEntry }[] }>('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_bulk_internal_transfer')
const onReconcile = useRefreshUnreconciledTransactions()
const addToActionLog = useUpdateActionLog()
const onSubmit = (data: { bank_account: string }) => {
createPaymentEntry({
bank_transaction_names: transactions.map((transaction) => transaction.name),
bank_account: data.bank_account
}).then(({ message }) => {
addToActionLog({
type: 'transfer',
timestamp: (new Date()).getTime(),
isBulk: true,
items: message.map((item) => ({
bankTransaction: item.transaction,
voucher: {
reference_doctype: "Payment Entry",
reference_name: item.payment_entry.name,
posting_date: item.payment_entry.posting_date,
doc: item.payment_entry,
}
})),
bulkCommonData: {
bank_account: data.bank_account,
}
})
toast.success(_("Transfer Recorded"), {
duration: 4000,
closeButton: true,
})
onReconcile(transactions[transactions.length - 1])
setIsOpen(false)
})
}
const onAccountChange = (account: string) => {
form.setValue('bank_account', account)
}
const selectedAccount = useWatch({ control: form.control, name: 'bank_account' })
const currentCompany = useCurrentCompany()
const company = transactions && transactions.length > 0 ? transactions[0].company : (currentCompany ?? '')
console.log("This is here", transactions)
return <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className='flex flex-col gap-4'>
{error && <ErrorBanner error={error} />}
<SelectedTransactionsTable />
<BankOrCashPicker company={company} bankAccount={transactions[0]?.bank_account ?? ''} onAccountChange={onAccountChange} selectedAccount={selectedAccount} />
<DialogFooter>
<DialogClose asChild>
<Button size='md' variant={'outline'} disabled={loading}>{_("Cancel")}</Button>
</DialogClose>
<Button size='md' type='submit' disabled={loading}>{_("Transfer")}</Button>
</DialogFooter>
</div>
</form>
</Form>
}
interface InternalTransferFormFields extends PaymentEntry {
mirror_transaction_name?: string
}
const InternalTransferForm = ({ selectedBankAccount, selectedTransaction }: { selectedBankAccount: SelectedBank, selectedTransaction: UnreconciledTransaction }) => {
const setIsOpen = useSetAtom(bankRecTransferModalAtom)
const onClose = () => {
setIsOpen(false)
}
const { data: rule } = useGetRuleForTransaction(selectedTransaction)
const isWithdrawal = (selectedTransaction.withdrawal && selectedTransaction.withdrawal > 0) ? true : false
const form = useForm<InternalTransferFormFields>({
defaultValues: {
payment_type: 'Internal Transfer',
company: selectedTransaction?.company,
// If the transaction is a withdrawal, set the paid from to the selected bank account
paid_from: isWithdrawal ? selectedBankAccount.account : (rule?.account ?? ''),
// If the transaction is a deposit, set the paid to to the selected bank account
paid_to: !isWithdrawal ? selectedBankAccount.account : (rule?.account ?? ''),
// Set the amount to the amount of the selected transaction
paid_amount: selectedTransaction.unallocated_amount,
received_amount: selectedTransaction.unallocated_amount,
reference_date: selectedTransaction.date,
posting_date: selectedTransaction.date,
reference_no: (selectedTransaction.reference_number || selectedTransaction.description || '').slice(0, 140),
}
})
const onReconcile = useRefreshUnreconciledTransactions()
const { call: createPaymentEntry, loading, error, isCompleted } = useFrappePostCall<{ message: { transaction: BankTransaction, payment_entry: PaymentEntry } }>('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_internal_transfer')
const setBankRecUnreconcileModalAtom = useSetAtom(bankRecUnreconcileModalAtom)
const addToActionLog = useUpdateActionLog()
const { file: frappeFile } = useContext(FrappeContext) as FrappeConfig
const [isUploading, setIsUploading] = useState(false)
const [uploadProgress, setUploadProgress] = useState(0)
const [files, setFiles] = useState<File[]>([])
const onSubmit = (data: InternalTransferFormFields) => {
createPaymentEntry({
bank_transaction_name: selectedTransaction.name,
...data,
custom_remarks: data.remarks ? true : false,
// Pass this to reconcile both at the same time
mirror_transaction_name: data.mirror_transaction_name
}).then(async ({ message }) => {
addToActionLog({
type: 'transfer',
timestamp: (new Date()).getTime(),
isBulk: false,
items: [
{
bankTransaction: message.transaction,
voucher: {
reference_doctype: "Payment Entry",
reference_name: message.payment_entry.name,
reference_no: message.payment_entry.reference_no,
reference_date: message.payment_entry.reference_date,
posting_date: message.payment_entry.posting_date,
doc: message.payment_entry,
}
}
]
})
toast.success(_("Transfer Recorded"), {
duration: 4000,
closeButton: true,
action: {
label: _("Undo"),
onClick: () => setBankRecUnreconcileModalAtom(selectedTransaction.name)
},
actionButtonStyle: {
backgroundColor: "rgb(0, 138, 46)"
}
})
if (files.length > 0) {
setIsUploading(true)
const uploadPromises = files.map(f => {
return frappeFile.uploadFile(f, {
isPrivate: true,
doctype: "Payment Entry",
docname: message.payment_entry.name,
}, (_bytesUploaded, _totalBytes, progress) => {
setUploadProgress((currentProgress) => {
//If there are multiple files, we need to add the progress to the current progress
return currentProgress + ((progress?.progress ?? 0) / files.length)
})
})
})
return Promise.all(uploadPromises).then(() => {
setUploadProgress(0)
setIsUploading(false)
})
} else {
return Promise.resolve()
}
}).then(() => {
setUploadProgress(0)
setIsUploading(false)
onReconcile(selectedTransaction)
onClose()
})
}
useHotkeys('meta+s', () => {
form.handleSubmit(onSubmit)()
}, {
enabled: true,
preventDefault: true,
enableOnFormTags: true
})
const onAccountChange = (account: string, is_mirror: boolean = false) => {
//If the transaction is a withdrawal, set the paid to to the selected account - since this is the account where the money is deposited into
if (selectedTransaction.withdrawal && selectedTransaction.withdrawal > 0) {
form.setValue('paid_to', account)
} else {
form.setValue('paid_from', account)
}
if (!is_mirror) {
// Reset the mirror transaction name
form.setValue('mirror_transaction_name', '')
}
}
const selectedAccount = useWatch({ control: form.control, name: (selectedTransaction.deposit && selectedTransaction.deposit > 0) ? 'paid_from' : 'paid_to' })
const direction = useDirection()
if (isUploading && isCompleted) {
return <FileUploadBanner uploadProgress={uploadProgress} />
}
return <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className='flex flex-col gap-4'>
{error && <ErrorBanner error={error} />}
<div className='grid grid-cols-2 gap-4'>
<SelectedTransactionDetails transaction={selectedTransaction} />
<div className='flex flex-col gap-4'>
<div className='grid grid-cols-2 gap-4'>
<DateField
name='posting_date'
label={_("Posting Date")}
isRequired
inputProps={{ autoFocus: false }}
/>
<DateField
name='reference_date'
label={_("Reference Date")}
isRequired
inputProps={{ autoFocus: false }}
/>
</div>
<DataField name='reference_no' label={_("Reference")} isRequired inputProps={{ autoFocus: false }} />
</div>
</div>
<div className='flex flex-col gap-2'>
<H4 className='text-base'>{isWithdrawal ? _('Transferred to') : _('Transferred from')}</H4>
<RecommendedTransferAccount transaction={selectedTransaction} onAccountChange={onAccountChange} />
<BankOrCashPicker company={selectedTransaction.company ?? ''} bankAccount={selectedTransaction.bank_account ?? ''} onAccountChange={onAccountChange} selectedAccount={selectedAccount} />
</div>
<div className='flex flex-col gap-2 py-2'>
<div className='flex items-end justify-between gap-4'>
<div className='flex-1'>
<AccountFormField
name="paid_from"
label={_("Paid From")}
account_type={['Bank', 'Cash']}
readOnly={isWithdrawal}
filterFunction={(account) => account.name !== selectedBankAccount.account}
isRequired
/>
</div>
<div className='pb-2'>
{direction === 'ltr' ? <ArrowRight /> : <ArrowLeft />}
</div>
<div className='flex-1'>
<AccountFormField
name="paid_to"
label={_("Paid To")}
account_type={['Bank', 'Cash']}
isRequired
readOnly={!isWithdrawal}
filterFunction={(account) => account.name !== selectedBankAccount.account}
/>
</div>
</div>
</div>
<Separator />
<div className='flex flex-col gap-2'>
<div className='grid grid-cols-2 gap-4'>
<SmallTextField
name='remarks'
label={_("Custom Remarks")}
formDescription={_("This will be auto-populated if not set.")}
/>
<div
data-slot="form-item"
className="flex flex-col gap-2"
>
<Label>{_("Attachments")}</Label>
<FileDropzone files={files} setFiles={setFiles} />
</div>
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button size='md' variant={'outline'} disabled={loading}>{_("Cancel")}</Button>
</DialogClose>
<Button size='md' type='submit' disabled={loading}>{_("Transfer")}</Button>
</DialogFooter>
</div>
</form>
</Form>
}
const BankOrCashPicker = ({ bankAccount, onAccountChange, selectedAccount, company }: { selectedAccount: string, bankAccount: string, onAccountChange: (account: string) => void, company: string }) => {
const { banks } = useGetBankAccounts(undefined, (bank) => bank.name !== bankAccount)
return <div className='grid grid-cols-4 gap-4'>
{banks.map((bank) => (
<div
className={cn('border p-2 rounded-md flex items-center gap-2 cursor-pointer outline-[0.5px] transition-all duration-200 hover:bg-surface-gray-1 dark:hover:bg-surface-gray-3',
selectedAccount === bank.account ? 'border-outline-gray-5 outline-outline-gray-5 bg-surface-gray-1 dark:bg-surface-gray-3' : 'border-outline-gray-2 outline-outline-gray-2'
)}
role='button'
key={bank.account}
onClick={() => onAccountChange(bank.account ?? '')}
>
<BankLogo bank={bank} iconSize='24px' imageClassName='w-12 h-12' />
<div className='flex flex-col gap-1'>
<span className='font-semibold text-sm'>{bank.account_name} {bank.bank_account_no && <span className='text-xs text-ink-gray-5'>({bank.bank_account_no})</span>}</span>
<span className='text-xs text-ink-gray-5'>{bank.account}</span>
</div>
</div>
))}
<CashPicker company={company ?? ''} selectedAccount={selectedAccount} setSelectedAccount={onAccountChange} />
</div>
}
const CashPicker = ({ company, selectedAccount, setSelectedAccount }: { company: string, selectedAccount: string, setSelectedAccount: (account: string) => void }) => {
const { data } = useFrappeGetCall('frappe.client.get_value', {
doctype: 'Company',
filters: company,
fieldname: 'default_cash_account'
}, undefined, {
revalidateOnFocus: false,
revalidateIfStale: false,
})
const account = data?.message?.default_cash_account
if (account) {
return <div className={cn('border p-2 rounded-md flex items-center gap-2 cursor-pointer outline-[0.5px] transition-all duration-200 hover:bg-surface-gray-1 dark:hover:bg-surface-gray-3',
selectedAccount === account ? 'border-outline-gray-5 outline-outline-gray-5 bg-surface-gray-1 dark:bg-surface-gray-3' : 'border-outline-gray-2 outline-outline-gray-2'
)}
role='button'
onClick={() => setSelectedAccount(account ?? '')}
>
<div className='flex items-center justify-center h-10 w-10'>
<Banknote size='24px' />
</div>
<div className='flex flex-col gap-1'>
<span className='font-semibold text-sm'>Cash</span>
<span className='text-xs text-ink-gray-5'>{data?.message?.default_cash_account}</span>
</div>
</div>
}
return null
}
const RecommendedTransferAccount = ({ transaction, onAccountChange }: { transaction: UnreconciledTransaction, onAccountChange: (account: string, is_mirror: boolean) => void }) => {
const { setValue, watch } = useFormContext<InternalTransferFormFields>()
const mirrorTransactionName = watch('mirror_transaction_name')
const paid_from = watch('paid_from')
const paid_to = watch('paid_to')
const { data } = useFrappeGetCall('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.search_for_transfer_transaction', {
transaction_id: transaction.name
}, undefined, {
revalidateOnFocus: false,
revalidateIfStale: false,
})
// Get bank accounts to find the logo
const { banks } = useGetBankAccounts()
const bank = useMemo(() => {
if (data?.message?.bank_account && banks) {
return banks.find(bank => bank.name === data.message.bank_account)
}
return null
}, [data?.message?.bank_account, banks])
const selectTransaction = () => {
if (data?.message) {
setValue('mirror_transaction_name', data.message.name)
onAccountChange(data.message.account, true)
}
}
if (data?.message) {
const isWithdrawal = data.message.withdrawal && data.message.withdrawal > 0
const amount = isWithdrawal ? data.message.withdrawal : data.message.deposit
const currency = data.message.currency
const isAccountSelected = isWithdrawal ? paid_from === data.message.account : paid_to === data.message.account
const isSuggested = mirrorTransactionName === data?.message?.name && isAccountSelected
return (<div className='pb-2'>
<div className={cn("flex justify-between items-start gap-3 p-3 border rounded-lg shadow-sm",
isSuggested ? "border-outline-green-4 bg-surface-green-1" : "border-outline-violet-2 bg-surface-violet-2/50")}>
<div>
<div className='flex flex-col gap-3'>
<div className={cn("flex items-center gap-2 shrink-0",
isSuggested ? "text-ink-green-4" : "text-ink-violet-4"
)}>
<BadgeCheck className="w-4 h-4" />
<span className="text-sm font-medium">{_("Suggested Transfer to {0}", [data.message.account])}</span>
</div>
<div className='flex flex-col gap-1'>
<span className='text-p-sm'>{_("The system found a mirror transaction ({0}) in another account with the same amount and date.", [data.message.name])}</span>
<span className='text-p-sm'>{_("Accepting the suggestion will reconcile both transactions.")}</span>
</div>
<div className='flex flex-col gap-1.5'>
<div className='flex items-center gap-1'>
<Calendar size='16px' />
<span className='text-sm'>{formatDate(data.message.date, 'Do MMM YYYY')}</span>
</div>
<span className='text-sm line-clamp-1' title={data.message.description}>{data.message.description}</span>
</div>
</div>
</div>
<div className='flex flex-col items-end justify-between gap-2 h-full w-[30%]'>
<div className="flex items-center gap-2">
<BankLogo bank={bank} iconSize='24px' imageClassName='h-8 max-w-24' iconClassName={cn(isSuggested ? "text-ink-green-3" : "text-purple-600")} />
</div>
<div className='flex gap-1'>
<div className={cn('flex items-center gap-1 text-end px-0 justify-end py-1 rounded-sm',
isWithdrawal ? 'text-ink-red-3' : 'text-ink-green-3'
)}>
{isWithdrawal ? <ArrowUpRight className="w-5 h-5 text-ink-red-3" /> : <ArrowDownRight className="w-5 h-5 text-ink-green-3" />}
<span className='text-sm font-semibold uppercase'>{isWithdrawal ? _('Transferred Out') : _('Received')}</span>
</div>
</div>
<span className='font-semibold font-numeric text-lg text-end pe-0.5'>{formatCurrency(amount, currency)}</span>
<div className='pt-1'>
<Button
onClick={selectTransaction}
theme={isSuggested ? "green" : "violet"}
size="md"
type='button'
>
{isSuggested ? <CheckCircle /> : <CheckIcon />}
{isSuggested ? _("Accepted") : _("Use Suggestion")}
</Button>
</div>
</div>
</div>
</div>
)
}
return null
}
export default TransferModal

View File

@@ -0,0 +1,85 @@
import { BankAccount } from "@/types/Accounts/BankAccount";
import { getDatesForTimePeriod } from "@/lib/date";
import { atom } from "jotai";
import { atomWithStorage, createJSONStorage } from "jotai/utils";
import { atomFamily } from 'jotai-family'
import { UnreconciledTransaction } from "./utils";
import { BankTransaction } from "@/types/Accounts/BankTransaction";
import { PaymentEntry } from "@/types/Accounts/PaymentEntry";
import { JournalEntry } from "@/types/Accounts/JournalEntry";
export interface SelectedBank extends Pick<BankAccount, 'name' | 'bank' | 'is_credit_card' | 'company' | 'account_name' | 'bank_account_no' | 'account' | 'account_type' | 'integration_id' | 'is_default' | 'last_integration_date'> {
logo?: string,
logoDark?: string,
darkModeInvert?: boolean,
logoClassName?: string,
account_currency?: string
}
export const selectedBankAccountAtom = atomWithStorage<SelectedBank | null>('bank-rec-selected-bank', null, undefined, {
getOnInit: true
})
export const bankRecDateAtom = atomWithStorage<{ fromDate: string, toDate: string }>("bank-rec-date", {
fromDate: getDatesForTimePeriod('This Month').fromDate,
toDate: getDatesForTimePeriod('This Month').toDate
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const bankRecClosingBalanceAtom = atomFamily((_id: string) => {
return atom<{ value: number, stringValue: string | number | undefined }>({
value: 0,
stringValue: '0.00'
})
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const bankRecSelectedTransactionAtom = atomFamily((_id: string) => {
return atom<UnreconciledTransaction[]>([])
})
/** Action Modals */
export const bankRecTransferModalAtom = atom(false)
export const bankRecRecordPaymentModalAtom = atom(false)
export const bankRecRecordJournalEntryModalAtom = atom(false)
export const bankRecUnreconcileModalAtom = atom<string>('')
export const bankRecMatchFilters = atomWithStorage<string[]>('bank-rec-match-filters', ['payment_entry', 'journal_entry'])
export const bankRecSearchText = atom<string>('')
export const bankRecAmountFilter = atom<{ value: number, stringValue?: string | number }>({
value: 0,
stringValue: '0.00'
})
export const bankRecTransactionTypeFilter = atom<string>('All')
export interface ActionLog {
type: 'match' | 'payment' | 'transfer' | 'bank_entry'
isBulk: boolean
timestamp: number,
items: ActionLogItem[],
bulkCommonData?: {
party_type?: string,
party?: string,
account?: string,
bank_account?: string,
}
}
export interface ActionLogItem {
bankTransaction: BankTransaction,
voucher: {
reference_doctype: string,
reference_name: string,
reference_no?: string,
reference_date?: string,
posting_date: string,
doc?: PaymentEntry | JournalEntry
},
}
const actionLogStorage = createJSONStorage<ActionLog[]>(() => sessionStorage)
export const bankRecActionLog = atomWithStorage<ActionLog[]>('bank-rec-action-log', [], actionLogStorage, {
getOnInit: true,
})

View File

@@ -0,0 +1,410 @@
export const BANK_LOGOS: { keywords: string[], logo: string, locale?: string[], logoDark?: string, darkModeInvert?: boolean, logoClassName?: string }[] = [
// United States + International
{
keywords: ['American Express', 'Amex'],
logo: 'Amex.svg',
locale: ['Global', 'United States']
},
{
keywords: ['Bank of America', 'BOA'],
logo: 'Bank_of_America.png',
darkModeInvert: true,
locale: ['United States']
},
{
keywords: ['Barclays'],
logo: 'Barclays.svg',
locale: ['Global', 'United Kingdom'],
logoClassName: 'h-12',
},
{
keywords: ['BNP Paribas'],
logo: 'BNP_Paribas.svg',
logoDark: 'BNP_Paribas-Dark.svg',
locale: ['Global', 'France'],
logoClassName: 'max-w-24'
},
{
keywords: ['Bank of New York Mellon', 'BNY Mellon', 'BNY'],
logo: 'BNY_Mellon.svg',
locale: ['Global', 'United States'],
logoDark: 'BNY_Mellon-Dark.svg',
},
{
keywords: ['Capital One'],
logo: 'Capital_One.png',
locale: ['United States'],
darkModeInvert: true
},
{
keywords: ['Charles Schwab', 'Schwab'],
logo: 'Charles_Schwab.svg',
locale: ['United States'],
logoClassName: 'h-7'
},
{
keywords: ['Chase'],
logo: 'chase.svg',
locale: ['Global', 'United States'],
logoDark: 'chase-Dark.svg',
},
{
keywords: ['Citi', 'Citibank', 'Citi Group', 'Citi Financial Services'],
logo: 'Citi.svg',
locale: ['Global', 'United States']
},
{
keywords: ['Deutsche Bank'],
logo: 'Deutsche_Bank.svg',
locale: ['Global', 'Germany'],
darkModeInvert: true,
},
{
keywords: ['Goldman Sachs'],
logo: 'Goldman_Sachs.svg',
locale: ['Global', 'United States'],
darkModeInvert: true,
},
{
keywords: ['HSBC'],
logo: 'HSBC.svg',
locale: ['Global', 'United Kingdom'],
logoDark: 'HSBC-dark.svg',
},
{
keywords: ['JPMorgan Chase', 'JPMorgan', 'JP Morgan', 'JP Morgan Chase', 'JPMorgan Chase & Co', 'JPM', 'JPMC'],
logo: 'jpmc.svg',
locale: ['Global', 'United States'],
darkModeInvert: true,
},
{
keywords: ['Morgan Stanley'],
logo: 'Morgan_Stanley.png',
locale: ['Global', 'United States'],
darkModeInvert: true,
},
{
keywords: ['PNC', 'PNC Financial Services Group', 'PNC Financial Services', 'Pittsburgh National Corporation'],
logo: 'PNC.png',
locale: ['United States']
},
{
keywords: ['Santander'],
logo: 'Santander.svg',
locale: ['Global']
},
{
keywords: ['TD Bank', 'Toronto Dominion Bank'],
logo: 'Toronto_Dominion_Bank.png',
locale: ['Canada']
},
{
keywords: ['Truist'],
logo: 'Truist.svg',
locale: ['United States'],
darkModeInvert: true,
logoClassName: 'h-8'
},
{
keywords: ['UBS'],
logo: 'UBS.svg',
locale: ['Global', 'Switzerland'],
logoDark: 'UBS-dark.svg',
},
{
keywords: ['US Bank', 'USBank', 'U.S. Bank', 'U.S. Bancorp'],
logo: 'USBank.svg',
locale: ['United States'],
logoDark: 'USBank-dark.svg',
},
{
keywords: ['Wells Fargo', 'Wells Fargo'],
logo: 'Wells_Fargo.svg',
locale: ['United States'],
logoClassName: 'h-7'
},
{
keywords: ['OakStar', 'Oakstar', 'Oakstar'],
logo: 'Oakstar.png',
logoDark: 'Oakstar-dark.webp',
locale: ['United States'],
logoClassName: 'h-7'
},
{
keywords: ['PlainsCapital', 'Plains Capital'],
logo: 'PlainsCapitalBank.png',
locale: ['United States'],
logoClassName: 'h-7'
},
{
keywords: ["Standard Chartered"],
logo: 'Standard_Chartered.png',
logoDark: 'Standard_Chartered-dark.png',
locale: ['Global'],
},
// India
{
keywords: ['HDFC Bank', 'HDFC'],
logo: 'HDFC.svg',
locale: ['India'],
},
{
keywords: ['ICICI Bank', 'ICICI'],
logo: 'ICICI.svg',
logoDark: 'ICICI-dark.svg',
locale: ['India'],
},
{
keywords: ['SBI', 'State Bank of India'],
logo: 'State_Bank_of_India.svg',
logoDark: 'State_bank_of_India-Dark.svg',
locale: ['India'],
logoClassName: 'h-4.5'
},
{
keywords: ['Punjab National Bank', 'PNB'],
logo: 'Punjab_National_Bank.svg',
locale: ['India']
},
{
keywords: ['Union Bank of India', 'Union Bank'],
logo: 'Union_Bank_of_India.svg',
locale: ['India']
},
{
keywords: ['Yes Bank', 'Yes'],
logo: 'Yes_Bank.svg',
locale: ['India'],
logoDark: 'Yes_Bank-dark.svg',
},
{
keywords: ['RBL Bank', 'RBL'],
logo: 'RBL_Bank.svg',
locale: ['India'],
logoDark: 'RBL_Bank-dark.svg',
},
{
keywords: ['Axis Bank', 'Axis'],
logo: 'Axis_Bank.svg',
locale: ['India'],
darkModeInvert: true
},
{
keywords: ['Bank of Baroda', 'BOB'],
logo: 'Bank_of_Baroda.svg',
locale: ['India', 'Kenya'],
logoClassName: 'h-7'
},
{
keywords: ['Bank of India', 'BOI'],
logo: 'Bank_of_India.png',
locale: ['India'],
logoClassName: 'h-7'
},
{
keywords: ['Bank of Maharashtra', 'BOM'],
logo: 'Bank_of_Maharashtra.png',
locale: ['India'],
logoClassName: 'min-w-24'
},
{
keywords: ['Kotak Mahindra Bank', 'Kotak'],
logo: 'Kotak_Mahindra.svg',
locale: ['India']
},
{
keywords: ['IndusInd Bank', 'IndusInd'],
logo: 'IndusInd_Bank.svg',
locale: ['India'],
darkModeInvert: true,
},
{
keywords: ['IDBI Bank', 'IDBI'],
logo: 'IDBI_Bank.svg',
locale: ['India']
},
{
keywords: ['IDFC First Bank', 'IDFC First'],
logo: 'IDFC_First_Bank.svg',
locale: ['India']
},
{
keywords: ['Federal Bank'],
logo: 'Federal_Bank.png',
logoDark: 'Federal_Bank-dark.png',
locale: ['India']
},
{
keywords: ['Fi Bank'],
logo: 'Fi_Bank.svg',
locale: ['India']
},
{
keywords: ['RazorpayX', 'Razorpay'],
logo: 'Razorpay.svg',
logoDark: 'Razorpay-dark.svg',
locale: ['India']
},
{
keywords: ['Revolut'],
logo: 'Revolut.png',
locale: ['Global'],
darkModeInvert: true
},
{
keywords: ['Starling Bank'],
logo: 'Starling_Bank.png',
logoDark: 'Starling_Bank-dark.png',
locale: ['Global', 'UK'],
logoClassName: 'h-10'
},
// Australia and New Zealand
{
keywords: ["Commonwealth Bank", "CBA"],
logo: "Commonwealth_Bank.svg",
locale: ['Australia', 'New Zealand'],
},
{
keywords: ["Airwallex"],
logo: "Airwallex.png",
logoDark: "Airwallex-dark.png",
locale: ['Global']
},
{
keywords: ["Judo Bank"],
logo: "Judo_Bank.svg",
logoDark: "Judo_Bank-dark.svg",
locale: ['Australia', 'New Zealand']
},
{
keywords: ["Alpha"], // This might conflict with Alpha Bank in Greece
logo: "Alpha_Bank.svg",
darkModeInvert: true,
logoClassName: 'h-4.5',
locale: ['Australia', 'New Zealand']
},
{
keywords: ["Australian Tax Office", "Australian Taxation Office"],
logo: "Australian_Tax_Office.png",
darkModeInvert: true,
locale: ['Australia']
},
{
keywords: ["Westpac"],
logo: "Westpac.svg",
locale: ['Australia']
},
{
keywords: ["ANZ", "ANZ Bank", "Australia and New Zealand Banking Group"],
logo: "ANZ.png",
locale: ['Australia', 'New Zealand']
},
{
keywords: ["Macquarie Group", "Macquarie Bank"],
logo: "Macquarie.svg",
darkModeInvert: true,
locale: ['Australia']
},
// Nicaragua
{
keywords: ["Banco Atlantida", "Banco Atlántida"],
logo: "Banco_Atlantida.png",
locale: ['Nicaragua']
},
{
keywords: ["Banco de Finanzas"],
logo: "Banco_de_Finanzas.svg",
locale: ['Nicaragua'],
logoClassName: 'h-4.5'
},
{
keywords: ["Avanz"],
logo: "Avanz.svg",
logoDark: "Avanz-dark.svg",
locale: ['Nicaragua'],
logoClassName: 'h-7'
},
{
keywords: ["Ficohsa"],
logo: "Ficohsa.svg",
locale: ['Nicaragua']
},
{
keywords: ["BAC", "BAC Credomatic"],
logo: "BAC_Credomatic.svg",
locale: ['Nicaragua'],
logoClassName: 'h-4.5'
},
{
keywords: ["Banco Lafise"],
logo: "Banco_Lafise.png",
darkModeInvert: true,
locale: ['Nicaragua']
},
// German
{
keywords: ["Sparkasse"],
logo: "Sparkasse.png",
locale: ['Germany']
},
{
keywords: ["Volksbank", "Raiffeisenbank", "VR-Bank"],
logo: "Volksbanken_Raiffeisenbanken.svg",
locale: ['Germany'],
logoClassName: 'min-w-32'
},
// Kenya
{
keywords: ["KCB Bank", "KCB"],
logo: "KCB_Bank_Kenya.png",
locale: ['Kenya']
},
{
keywords: ["Equity Bank"],
logo: "Equity_Bank.png",
logoDark: "Equity_Bank-dark.png",
locale: ['Kenya'],
},
{
keywords: ["I&M"],
logo: "I&M.png",
locale: ['Kenya']
},
{
keywords: ["ABSA"],
logo: "ABSA.png",
locale: ['Kenya'],
darkModeInvert: true,
logoClassName: 'h-7'
},
{
keywords: ["Stanbic"],
logo: "Stanbic.png",
locale: ['Kenya'],
logoClassName: 'h-7'
},
{
keywords: ["DTB", "Diamond Trust Bank"],
logo: "Diamond_Trust_Bank.png",
locale: ['Kenya']
},
{
keywords: ["Prime Bank"],
logo: "Prime_Bank.png",
locale: ['Kenya'],
logoClassName: 'max-w-28'
},
{
keywords: ["Stripe"],
logo: "Stripe.svg",
locale: ['Global'],
logoClassName: 'h-6',
darkModeInvert: true,
},
{
keywords: ["PayPal"],
logo: "PayPal.png",
locale: ['Global'],
logoClassName: 'h-6',
}
]

View File

@@ -0,0 +1,457 @@
import { ActionLog, bankRecActionLog, bankRecAmountFilter, bankRecDateAtom, bankRecMatchFilters, bankRecSearchText, bankRecSelectedTransactionAtom, bankRecTransactionTypeFilter, bankRecUnreconcileModalAtom, SelectedBank, selectedBankAccountAtom } from './bankRecAtoms'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { useMemo } from 'react'
import { SWRConfiguration, useFrappeGetCall, useFrappeGetDoc, useFrappePostCall, useSWRConfig } from 'frappe-react-sdk'
import { BankTransaction } from '@/types/Accounts/BankTransaction'
import { BankAccount } from '@/types/Accounts/BankAccount'
import dayjs from 'dayjs'
import { toast } from 'sonner'
import { BANK_LOGOS } from './logos'
import { getErrorMessage } from '@/lib/frappe'
import { useCurrentCompany } from '@/hooks/useCurrentCompany'
import _ from '@/lib/translate'
import { BankTransactionRule } from '@/types/Accounts/BankTransactionRule'
import { useRef } from 'react'
import type { DebouncedState } from 'usehooks-ts'
import { useDebounceCallback } from 'usehooks-ts'
import Fuse from 'fuse.js'
export const useGetAccountOpeningBalance = () => {
const companyID = useCurrentCompany()
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
const args = useMemo(() => {
return {
bank_account: bankAccount?.name,
company: companyID,
till_date: dayjs(dates.fromDate).subtract(1, 'days').format('YYYY-MM-DD'),
}
}, [companyID, bankAccount?.name, dates.fromDate])
return useFrappeGetCall('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance', args, undefined, {
revalidateOnFocus: false
})
}
export const useGetAccountClosingBalance = () => {
const companyID = useCurrentCompany()
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
const args = useMemo(() => {
return {
bank_account: bankAccount?.name,
company: companyID,
till_date: dates.toDate,
}
}, [companyID, bankAccount?.name, dates.toDate])
return useFrappeGetCall('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance', args,
`bank-reconciliation-account-closing-balance-${bankAccount?.name}-${dates.toDate}`,
{
revalidateOnFocus: false
}
)
}
/**
* Hook to fetch the closing balance set in the database for the given bank and date
*/
export const useGetAccountClosingBalanceAsPerStatement = (swrConfig: SWRConfiguration = {}) => {
const dates = useAtomValue(bankRecDateAtom)
const bankAccount = useAtomValue(selectedBankAccountAtom)
return useFrappeGetCall<{ message: { balance: number, date?: string } }>("erpnext.accounts.doctype.bank_account.bank_account.get_closing_balance_as_per_statement", {
bank_account: bankAccount?.name,
date: dates.toDate
}, `bank-reconciliation-account-closing-balance-as-per-statement-${bankAccount?.name}-${dates.toDate}`, {
revalidateOnFocus: false,
...swrConfig
})
}
export type UnreconciledTransaction = Pick<BankTransaction, 'name' | 'matched_transaction_rule' | 'date' | 'withdrawal' | 'deposit' | 'currency' | 'description' | 'status' | 'transaction_type' | 'reference_number' | 'party_type' | 'party' | 'bank_account' | 'company' | 'unallocated_amount'>
export const useGetUnreconciledTransactions = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
return useFrappeGetCall<{ message: UnreconciledTransaction[] }>('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_bank_transactions', {
bank_account: bankAccount?.name,
from_date: dates.fromDate,
to_date: dates.toDate
}, bankAccount ? `bank-reconciliation-unreconciled-transactions-${bankAccount?.name}-${dates.fromDate}-${dates.toDate}` : null, {
revalidateOnFocus: false,
revalidateIfStale: false
})
}
export interface LinkedPayment {
rank: number,
doctype: string,
name: string,
paid_amount: number,
reference_no: string,
reference_date: string,
posting_date: string,
party_type?: string,
party?: string,
currency: string
}
export const useGetBankTransactions = () => {
const bankAccount = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
return useFrappeGetCall<{ message: BankTransaction[] }>('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_bank_transactions', {
bank_account: bankAccount?.name,
from_date: dates.fromDate,
to_date: dates.toDate,
all_transactions: true
}, bankAccount ? `bank-reconciliation-bank-transactions-${bankAccount?.name}-${dates.fromDate}-${dates.toDate}` : null)
}
export const useGetVouchersForTransaction = (transaction: UnreconciledTransaction) => {
const dates = useAtomValue(bankRecDateAtom)
const matchFilters = useAtomValue(bankRecMatchFilters)
return useFrappeGetCall<{ message: LinkedPayment[] }>('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_linked_payments', {
bank_transaction_name: transaction.name,
document_types: matchFilters ?? ['payment_entry', 'journal_entry'],
from_date: dates.fromDate,
to_date: dates.toDate,
filter_by_reference_date: 0
}, `bank-reconciliation-vouchers-${transaction.name}-${dates.fromDate}-${dates.toDate}-${matchFilters.join(',')}`, {
revalidateOnFocus: false
})
}
/**
* Common hook to refresh the unreconciled transactions list after a transaction is reconciled
* @returns function to call to refresh the unreconciled transactions list AFTER the operation is done
*/
export const useRefreshUnreconciledTransactions = () => {
const selectedBank = useAtomValue(selectedBankAccountAtom)
const dates = useAtomValue(bankRecDateAtom)
const matchFilters = useAtomValue(bankRecMatchFilters)
const setSelectedTransaction = useSetAtom(bankRecSelectedTransactionAtom(selectedBank?.name || ''))
const { mutate } = useSWRConfig()
const searchString = useAtomValue(bankRecSearchText)
const typeFilter = useAtomValue(bankRecTransactionTypeFilter)
const amountFilter = useAtomValue(bankRecAmountFilter)
const { data: unreconciledTransactions } = useGetUnreconciledTransactions()
/**
* This function should be called after a transaction is reconciled
* It will get the next unreconciled transaction and select it
* And then refresh the balance + unreconciled transactions list
*/
const onReconcileTransaction = (transaction: UnreconciledTransaction, updatedTransaction?: BankTransaction) => {
// If the updated transaction has an unallocated amount of 0, then we need to select the next unreconciled transaction
if (updatedTransaction && updatedTransaction?.unallocated_amount !== 0) {
mutate(`bank-reconciliation-unreconciled-transactions-${selectedBank?.name}-${dates.fromDate}-${dates.toDate}`)
mutate(`bank-reconciliation-account-closing-balance-${selectedBank?.name}-${dates.toDate}`)
// Update the matching vouchers for the selected transaction
mutate(`bank-reconciliation-vouchers-${transaction.name}-${dates.fromDate}-${dates.toDate}-${matchFilters.join(',')}`)
return
}
// From unreconciled transactions list, first apply the filters based on the search criteria and other filters
const searchIndex = unreconciledTransactions ? new Fuse(unreconciledTransactions.message, {
keys: ['description', 'reference_number'],
threshold: 0.5,
includeScore: true
}) : null
const results = getSearchResults(searchIndex, searchString, typeFilter, amountFilter.value, unreconciledTransactions?.message)
const currentIndex = results.findIndex(t => t.name === transaction.name)
let nextTransaction = null
if (currentIndex !== -1) {
// Check if there is a next transaction
if (currentIndex < (results.length || 0) - 1) {
nextTransaction = results[currentIndex + 1]
}
}
// We need to select the next unreconciled transaction for a better UX
mutate(`bank-reconciliation-unreconciled-transactions-${selectedBank?.name}-${dates.fromDate}-${dates.toDate}`)
.then(res => {
if (nextTransaction) {
// Check if next transaction is there in the response
const nextTransactionObj = res?.message.find((t: UnreconciledTransaction) => t.name === nextTransaction.name)
if (nextTransactionObj) {
setSelectedTransaction([nextTransactionObj])
} else {
// If the next transaction is not there in the response, we need to clear the selection
setSelectedTransaction([])
}
} else {
// If there is no next transaction, we need to clear the selection
setSelectedTransaction([])
}
})
mutate(`bank-reconciliation-account-closing-balance-${selectedBank?.name}-${dates.toDate}`)
}
return onReconcileTransaction
}
export const useReconcileTransaction = () => {
const { call, loading } = useFrappePostCall<{ message: BankTransaction }>('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.reconcile_vouchers')
const onReconcileTransaction = useRefreshUnreconciledTransactions()
const setBankRecUnreconcileModalAtom = useSetAtom(bankRecUnreconcileModalAtom)
const addToActionLog = useUpdateActionLog()
const reconcileTransaction = (transaction: UnreconciledTransaction, voucher: LinkedPayment) => {
call({
bank_transaction_name: transaction.name,
vouchers: JSON.stringify([{
"payment_doctype": voucher.doctype,
"payment_name": voucher.name,
"amount": voucher.paid_amount
}])
}).then((res) => {
addToActionLog({
type: 'match',
timestamp: (new Date()).getTime(),
isBulk: false,
items: [
{
bankTransaction: res.message,
voucher: {
reference_doctype: voucher.doctype,
reference_name: voucher.name,
reference_no: voucher.reference_no,
reference_date: voucher.reference_date,
posting_date: voucher.posting_date,
}
}
]
})
onReconcileTransaction(transaction, res.message)
toast.success(_("Reconciled"), {
duration: 4000,
closeButton: true,
action: {
label: _("Undo"),
onClick: () => setBankRecUnreconcileModalAtom(transaction.name)
},
actionButtonStyle: {
backgroundColor: "rgb(0, 138, 46)"
}
})
}).catch((error) => {
console.error(error)
toast.error(_("Error"), {
duration: 5000,
description: getErrorMessage(error)
})
})
}
return { reconcileTransaction, loading }
}
interface BankAccountWithCurrency extends Pick<BankAccount, 'name' | 'bank' | 'account_name' | 'is_credit_card' | 'company' | 'account' | 'account_type' | 'account_subtype' | 'bank_account_no' | 'last_integration_date'> {
account_currency?: string
}
type BankLogoEntry = (typeof BANK_LOGOS)[number]
/** Prefer the longest keyword match so short tokens (e.g. "anz" in "finanzas") do not beat full bank names. */
function findBankLogoForName(bankName: string | undefined | null): BankLogoEntry | undefined {
if (!bankName) return undefined
const haystack = bankName.toLowerCase()
let best: BankLogoEntry | undefined
let bestKeywordLen = 0
for (const entry of BANK_LOGOS) {
for (const keyword of entry.keywords) {
const needle = keyword.toLowerCase()
if (needle.length === 0) continue
if (haystack.includes(needle) && needle.length > bestKeywordLen) {
bestKeywordLen = needle.length
best = entry
}
}
}
return best
}
export const useGetBankAccounts = (onSuccess?: (data?: Omit<SelectedBank, 'logo'>[]) => void, filterFn?: (bank: SelectedBank) => boolean) => {
const company = useCurrentCompany()
const { data, isLoading, error } = useFrappeGetCall<{ message: BankAccountWithCurrency[] }>('erpnext.accounts.doctype.bank_account.bank_account.get_list', {
company: company
}, undefined, {
revalidateOnFocus: false,
revalidateIfStale: false,
onSuccess: (data) => {
onSuccess?.(data?.message)
}
})
const banks = useMemo(() => {
// Match the bank account to the logo
const banksWithLogos = data?.message.map((bank) => {
const logo = findBankLogoForName(bank.bank)
return {
...bank,
logo: logo?.logo,
logoDark: logo?.logoDark,
darkModeInvert: logo?.darkModeInvert,
logoClassName: logo?.logoClassName
}
}) ?? []
if (filterFn) {
return banksWithLogos.filter(filterFn)
}
return banksWithLogos
}, [data, filterFn])
return {
banks,
isLoading,
error
}
}
export const useIsTransactionWithdrawal = (transaction: UnreconciledTransaction) => {
return useMemo(() => {
const isWithdrawal = transaction.withdrawal && transaction.withdrawal > 0
const isDeposit = transaction.deposit && transaction.deposit > 0
return {
amount: isWithdrawal ? transaction.withdrawal : transaction.deposit,
isWithdrawal,
isDeposit
}
}, [transaction])
}
export const useGetRuleForTransaction = (transaction: UnreconciledTransaction) => {
return useFrappeGetDoc<BankTransactionRule>('Bank Transaction Rule', transaction.matched_transaction_rule,
transaction.matched_transaction_rule ? undefined : null, {
revalidateOnFocus: false,
revalidateIfStale: false
}
)
}
/** Hook to handle the search input while maintaining debouncing and global state. */
export function useTransactionSearch(): [string, DebouncedState<(value: string) => void>] {
const delay = 500
const unwrappedInitialValue = ''
const eq = (left: string, right: string) => left === right
const [debouncedValue, setDebouncedValue] = useAtom(bankRecSearchText)
const previousValueRef = useRef<string | undefined>(unwrappedInitialValue)
const updateDebouncedValue = useDebounceCallback(
setDebouncedValue,
delay,
)
// Update the debounced value if the initial value changes
if (!eq(previousValueRef.current as string, unwrappedInitialValue)) {
updateDebouncedValue(unwrappedInitialValue)
previousValueRef.current = unwrappedInitialValue
}
return [debouncedValue, updateDebouncedValue]
}
/** Utility function to get the search results based on the search index, search string, type filter, amount filter and unreconciled transactions */
export const getSearchResults = (
/** Fuse index of the unreconciled transactions */
searchIndex: Fuse<UnreconciledTransaction> | null,
/** Search string */
search: string,
/** Type filter */
typeFilter: string,
/** Amount filter */
amountFilter: number,
/** Unreconciled transactions */
unreconciledTransactions?: UnreconciledTransaction[]) => {
let r = []
if (!searchIndex || !search) {
r = unreconciledTransactions ?? []
} else {
r = searchIndex.search(search).map((result) => result.item)
}
if (typeFilter !== 'All') {
r = r.filter((transaction) => {
if (typeFilter === 'Debits') {
return transaction.withdrawal && transaction.withdrawal > 0
}
if (typeFilter === 'Credits') {
return transaction.deposit && transaction.deposit > 0
}
})
}
if (amountFilter > 0) {
r = r.filter((transaction) => {
if (transaction.withdrawal && transaction.withdrawal > 0) {
return transaction.withdrawal === amountFilter
}
if (transaction.deposit && transaction.deposit > 0) {
return transaction.deposit === amountFilter
}
return false
})
}
return r
}
export const useUpdateActionLog = () => {
const setActionLog = useSetAtom(bankRecActionLog)
const addToActionLog = (action: ActionLog) => {
// Store at max 100 actions
setActionLog((prev) => {
const newActions = [action, ...prev]
if (newActions.length > 100) {
return newActions.slice(0, 100)
}
return newActions
})
}
return addToActionLog
}

View File

@@ -0,0 +1,22 @@
import CSVRawDataPreview from './CSVRawDataPreview'
import StatementDetails from './StatementDetails'
import _ from '@/lib/translate'
import { GetStatementDetailsResponse } from '../import_utils'
const CSVImport = ({ data }: { data: { message: GetStatementDetailsResponse } }) => {
return (
<div className="w-full flex">
<div className="w-[50%] p-4 h-[calc(100vh-72px)] overflow-scroll">
<StatementDetails data={data.message} />
</div>
<div className="w-[50%] border-s border-t pe-1 ps-0 border-outline-gray-2 h-[calc(100vh-72px)] overflow-scroll">
<CSVRawDataPreview data={data.message} />
</div>
</div>
)
}
export default CSVImport

View File

@@ -0,0 +1,151 @@
import { Table, TableBody, TableCell, TableHead, TableRow } from "@/components/ui/table"
import { cn } from "@/lib/utils"
import { ArrowDownRightIcon, ArrowUpDownIcon, ArrowUpRightIcon, BanknoteIcon, CalendarIcon, DollarSignIcon, FileTextIcon, ListIcon, ReceiptIcon } from "lucide-react"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import _ from "@/lib/translate"
import { GetStatementDetailsResponse } from "../import_utils"
import { useMemo } from "react"
import { BankStatementImportLogColumnMap } from "@/types/Accounts/BankStatementImportLogColumnMap"
const CSVRawDataPreview = ({ data }: { data: GetStatementDetailsResponse }) => {
const column_mapping: Record<StandardColumnTypes, number> = useMemo(() => {
const col_map: Record<string, number> = {}
data.doc.column_mapping?.forEach(col => {
if (col.maps_to && col.maps_to !== "Do not import") {
col_map[col.maps_to] = col.index;
}
})
return col_map
}, [data])
const validColumns = Object.values(column_mapping)
// Reverse the column mapping to get a map of column index to variable name
const columnIndexMap: Record<number, StandardColumnTypes> = Object.fromEntries(Object.entries(column_mapping).map(([variable, columnIndex]) => [columnIndex, variable as StandardColumnTypes]))
// Loop over the contents of the CSV file and show a preview - highlight the header row and the transaction rows
return (
<Table containerClassName="rounded-none">
<TableBody>
{data.raw_data.map((row, index) => {
const isHeaderRow = index === data.doc.detected_header_index;
const isTransactionRow = index >= (data.doc.detected_transaction_starting_index ?? 0) && index <= (data.doc.detected_transaction_ending_index ?? 0);
return <TableRow key={index}
title={isHeaderRow ? "Header Row" : ""}
className={cn({
// "bg-yellow-100": isHeaderRow,
// "hover:bg-yellow-100": isHeaderRow,
"bg-green-50 hover:bg-green-50 dark:bg-green-700 dark:hover:bg-green-700": isTransactionRow,
"text-ink-gray-5/70": !isTransactionRow && !isHeaderRow,
})}>
{isHeaderRow ? <TableHead className="bg-yellow-100 hover:bg-yellow-100 dark:bg-yellow-400 text-center font-semibold text-ink-gray-8">
{index + 1}
</TableHead> :
<TableCell className="text-center px-1 py-0.5">
{index + 1}
</TableCell>
}
{row.map((cell, cellIndex) => {
const isValidColumn = validColumns.includes(cellIndex);
const columnType = columnIndexMap[cellIndex];
const isAmountColumn = ["Amount", "Withdrawal", "Deposit", "Balance"].includes(columnType);
if (isHeaderRow) {
return <TableHead key={cellIndex} className={cn("max-w-[250px] w-fit overflow-hidden text-ellipsis py-0.5",
isValidColumn ? "bg-yellow-100 hover:bg-yellow-100 dark:bg-yellow-400" : "bg-surface-gray-2",
)}>
<div className={cn("flex items-center text-xs gap-1 px-1 text-ink-gray-8 font-medium", {
"justify-end": isAmountColumn && isValidColumn
})}>
{columnType && <Tooltip>
<TooltipTrigger>
<ColumnHeaderIcon columnType={columnType} />
</TooltipTrigger>
<TooltipContent>
{_(columnType)}
</TooltipContent>
</Tooltip>
}
{cell}
</div>
</TableHead>
} else {
return <TableCell key={cellIndex} className={cn("max-w-[200px] w-fit overflow-hidden text-ellipsis py-0.5",
{
"bg-green-100 dark:bg-green-400 hover:bg-green-100 dark:hover:bg-green-400": isValidColumn && isTransactionRow,
"text-ink-gray-5": !isValidColumn && isTransactionRow,
}
)} >
<div className={cn("min-h-5 flex items-center text-xs px-1", {
"justify-end": isAmountColumn && isValidColumn && isTransactionRow
})} title={cell}>
{cell}
</div>
</TableCell>
}
}
)}
</TableRow>
})}
</TableBody>
</Table >
)
}
type StandardColumnTypes = BankStatementImportLogColumnMap['maps_to'];
const ColumnHeaderIcon = ({ columnType }: { columnType?: StandardColumnTypes }) => {
if (!columnType) {
return null
}
if (columnType === 'Amount') {
return <DollarSignIcon className="w-4 h-4" />
}
if (columnType === 'Withdrawal') {
return <ArrowUpRightIcon className="w-4 h-4 text-ink-red-3" />
}
if (columnType === 'Deposit') {
return <ArrowDownRightIcon className="w-4 h-4 text-ink-green-3" />
}
if (columnType === 'Balance') {
return <BanknoteIcon className="w-4 h-4" />
}
if (columnType === 'Date') {
return <CalendarIcon className="w-4 h-4" />
}
if (columnType === 'Description') {
return <FileTextIcon className="w-4 h-4" />
}
if (columnType === 'Reference') {
return <ReceiptIcon className="w-4 h-4" />
}
if (columnType === 'Transaction Type') {
return <ListIcon className="w-4 h-4" />
}
if (columnType === 'Debit/Credit') {
return <ArrowUpDownIcon className="w-4 h-4" />
}
return null
}
export default CSVRawDataPreview

View File

@@ -0,0 +1,351 @@
import _ from '@/lib/translate'
import { GetStatementDetailsResponse } from '../import_utils'
import { flt, formatCurrency } from '@/lib/numbers'
import { formatDate } from '@/lib/date'
import { bankRecDateAtom } from '../../BankReconciliation/bankRecAtoms'
import { AlertCircleIcon, ChevronLeftIcon, ChevronRightIcon, ExternalLinkIcon, InfoIcon, Loader2Icon } from 'lucide-react'
import { H2, H3, Paragraph } from '@/components/ui/typography'
import { FileTypeIcon } from '@/components/ui/file-dropzone'
import { getFileExtension } from '@/lib/file'
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Separator } from '@/components/ui/separator'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useFrappeEventListener, useFrappePostCall } from 'frappe-react-sdk'
import { toast } from 'sonner'
import ErrorBanner from '@/components/ui/error-banner'
import { Link, useNavigate } from 'react-router-dom'
import { useMemo, useState } from 'react'
import { Progress } from '@/components/ui/progress'
import { useSetAtom } from 'jotai'
import { useDirection } from '@/components/ui/direction'
import BankLogo from '@/components/common/BankLogo'
import { useGetBankAccounts } from '../../BankReconciliation/utils'
import { BankStatementImportLog } from '@/types/Accounts/BankStatementImportLog'
import { Badge } from '@/components/ui/badge'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
const parseDateFormat = (dateFormat: string) => {
const charMap = {
"%d": "DD",
"%m": "MM",
"%Y": "YYYY",
"%y": "YY",
"%b": "MMM",
"%B": "MMMM",
}
let label = dateFormat
Object.keys(charMap).forEach((char) => {
label = label.replace(char, charMap[char as keyof typeof charMap])
})
return dateFormat
}
type Props = {
data: GetStatementDetailsResponse,
}
const StatementDetails = ({ data }: Props) => {
const dateFormat = parseDateFormat(data.date_format)
const { call, loading, error } = useFrappePostCall<{ docs: BankStatementImportLog[] }>('run_doc_method')
const navigate = useNavigate()
const setDates = useSetAtom(bankRecDateAtom)
const direction = useDirection()
const onImport = () => {
call({
docs: data.doc,
method: 'insert_transactions'
}).then((response) => {
const doc = response.docs ? response.docs[0] : undefined
if (doc && doc.start_date && doc.end_date) {
setDates({
fromDate: doc.start_date,
toDate: doc.end_date,
})
}
toast.success(_("Bank statement imported."))
navigate(`/`)
}).catch(() => {
toast.error(_("There was an error while importing the bank statement."))
})
}
const [progress, setProgress] = useState(0)
useFrappeEventListener("bank-rec-statement-import-progress", (event) => {
setProgress(event.progress)
})
const file_name = data.doc.file.split("/").pop() ?? ""
const { banks } = useGetBankAccounts()
const bank = useMemo(() => {
return banks?.find((bank) => bank.name === data.doc.bank_account)
}, [data.doc.bank_account, banks])
return (
<div className='flex flex-col gap-4'>
<div className='flex flex-col gap-4'>
<div className='flex justify-between items-center'>
<Button size='sm' variant='outline' asChild>
<Link to="/statement-importer">
{direction === 'ltr' ? <ChevronLeftIcon /> : <ChevronRightIcon />}
{_("Back")}
</Link>
</Button>
{data.doc.status === 'Completed' ? <Badge theme='green'>{_("Completed")}</Badge> :
<Button onClick={onImport} disabled={loading || data.final_transactions?.length === 0} size='sm' type='button'>
{loading ? <Loader2Icon className='size-4 animate-spin' /> : null}
{loading ? _("Importing...") : _("Import {0} transactions", [data.final_transactions?.length?.toString() || "0"])}</Button>
}
</div>
<div className='flex items-start gap-4'>
<div className='flex flex-col gap-1'>
<H2 className='text-lg border-0 p-0'>{_("Statement Details")}</H2>
<Paragraph className='text-p-sm'><span>
{_("We've auto-detected the details of the statement file.")}
</span><br />
<span>
{_("Please review the details below and click the 'Import' button to proceed.")}
</span>
</Paragraph>
</div>
</div>
{progress > 0 && <div className='flex flex-col gap-2'><Progress value={progress} max={100} size="lg" />
<span className='text-sm'>{_("Importing {0} transactions", [progress.toString()])}
</span>
</div>}
{error && <ErrorBanner error={error} />}
<Table>
<TableBody>
<TableRow>
<TableHead>{_("Bank Account")}</TableHead>
<TableCell>
<div className='flex items-center gap-2'>
<BankLogo bank={bank} />
<span className="tracking-tight text-sm font-medium">{bank?.account_name}</span>
<span title="GL Account" className="text-sm">{bank?.account}</span>
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Statement File")}</TableHead>
<TableCell>
<div className='flex items-center gap-2'>
<FileTypeIcon fileType={getFileExtension(file_name)} size='md' showBackground={false} />
{file_name}
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Transaction Dates")}</TableHead>
<TableCell>{_("{0} to {1}", [formatDate(data.doc.start_date, "Do MMMM YYYY"), formatDate(data.doc.end_date, "Do MMMM YYYY")])}</TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Number of Transactions")}</TableHead>
<TableCell>{data.doc.number_of_transactions}</TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Total Debits")}</TableHead>
<TableCell><span className='font-numeric'>{formatCurrency(flt(data.doc.total_debits, 2), data.currency)}</span> <span className='text-ink-gray-5 font-sans'>({data.doc.total_debit_transactions} {data.doc.total_debit_transactions === 1 ? _("transaction") : _("transactions")})</span></TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Total Credits")}</TableHead>
<TableCell><span className='font-numeric'>{formatCurrency(flt(data.doc.total_credits, 2), data.currency)}</span> <span className='text-ink-gray-5 font-sans'>({data.doc.total_credit_transactions} {data.doc.total_credit_transactions === 1 ? _("transaction") : _("transactions")})</span></TableCell>
</TableRow>
<TableRow>
<TableHead>{_("Closing Balance as of {}", [formatDate(data.doc.end_date, "Do MMMM YYYY")])}</TableHead>
<TableCell className='font-numeric'>{formatCurrency(flt(data.doc.closing_balance, 2), data.currency)}</TableCell>
</TableRow>
<TableRow>
<TableHead>
<div className='flex items-center gap-2'>
{_("Detected Amount Format")} <Tooltip>
<TooltipTrigger><InfoIcon size={16} /></TooltipTrigger>
<TooltipContent>
{_("The amount format detected in the statement file. This is used to parse the deposit and withdrawal values from each row.")}
</TooltipContent>
</Tooltip>
</div>
</TableHead>
<TableCell>{data.doc.detected_amount_format}</TableCell>
</TableRow>
<TableRow>
<TableHead>
<div className='flex items-center gap-2'>
{_("Detected Date Format")}
<Tooltip>
<TooltipTrigger><InfoIcon size={16} /></TooltipTrigger>
<TooltipContent>
{_("The date format detected in the statement file. This is used to parse the date values.")}
</TooltipContent>
</Tooltip>
</div>
</TableHead>
<TableCell>
{dateFormat || data.date_format} (e.g.{" "}
{formatDate(new Date(), dateFormat || "YYYY-MM-DD")})
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
{data.doc.status === "Not Started" ? <>
<ConflictingTransactions transactions={data.conflicting_transactions} />
<Separator />
<div className='flex flex-col gap-4'>
<div className='flex flex-col gap-1'>
<H3 className='text-base border-0 p-0'>{_("Preview Transactions")}</H3>
{data.final_transactions?.length === 1 ? (
<Paragraph className='text-p-sm'>{_("We've found 1 transaction in the statement file that will be imported into the system. Please review the details below and click the 'Import' button to proceed.")}</Paragraph>
) : (
<Paragraph className='text-p-sm'>{_("{0} transactions will be imported into the system. Please review the details below and click the 'Import' button to proceed.", [data.final_transactions?.length?.toString() || "0"])}</Paragraph>
)}
</div>
<div className='max-h-[400px] overflow-scroll pb-2'>
<Table>
<TableCaption>{_("Transactions to be imported into the system")}</TableCaption>
<TableHeader>
<TableRow>
<TableHead className='w-8'>#</TableHead>
<TableHead>{_("Date")}</TableHead>
<TableHead>{_("Description")}</TableHead>
<TableHead>{_("Ref.")}</TableHead>
<TableHead className='text-end'>{_("Withdrawal")}</TableHead>
<TableHead className='text-end'>{_("Deposit")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.final_transactions?.map((transaction, index) => (
<TableRow key={index}>
<TableCell className='w-8'>{index + 1}</TableCell>
<TableCell>{formatDate(transaction.date)}</TableCell>
<TableCell className='max-w-[200px] w-fit overflow-hidden text-ellipsis'>{transaction.description}</TableCell>
<TableCell className='max-w-[100px] w-fit overflow-hidden text-ellipsis'>{transaction.reference}</TableCell>
<TableCell className='text-end font-numeric'>{formatCurrency(transaction.withdrawal, data.currency)}</TableCell>
<TableCell className='text-end font-numeric'>{formatCurrency(transaction.deposit, data.currency)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</> : null}
</div>
)
}
const ConflictingTransactions = ({ transactions }: { transactions: GetStatementDetailsResponse["conflicting_transactions"] }) => {
if (transactions.length === 0) {
return null
}
return <>
<Alert theme="red">
<AlertCircleIcon />
<AlertTitle>{_("Conflicting Transactions")}</AlertTitle>
<AlertDescription>
{transactions.length === 1 ? _("We've found 1 existing transaction in the system that conflicts with the transactions in the statement file. Are you sure you want to proceed with the import?")
: _("We've found {0} existing transactions in the system that conflict with the transactions in the statement file. Are you sure you want to proceed with the import?", [transactions.length.toString()])}
<div className='py-2'>
<Dialog>
<DialogTrigger asChild>
<Button
size='sm'
type='button'
theme='red'
variant='solid'>
<span>{transactions.length > 1 ? _("View transactions") : _("View transaction")}</span>
</Button>
</DialogTrigger>
<DialogContent className='min-w-7xl'>
<DialogHeader>
<DialogTitle>{_("Conflicting Transactions")}</DialogTitle>
<DialogDescription>
{transactions.length === 1 ? _("We've found 1 existing transaction in the system that conflicts with the transactions in the statement file. Are you sure you want to proceed with the import?")
: _("We've found {0} existing transactions in the system that conflict with the transactions in the statement file. Are you sure you want to proceed with the import?", [transactions.length.toString()])}
</DialogDescription>
</DialogHeader>
<div className='max-h-[400px] overflow-scroll pb-2'>
<Table>
<TableCaption>{_("Existing transactions in the system belonging to the same bank account and date range")}</TableCaption>
<TableHeader>
<TableRow>
<TableHead>{_("Date")}</TableHead>
<TableHead>{_("Description")}</TableHead>
<TableHead>{_("Ref.")}</TableHead>
<TableHead className='text-end'>{_("Withdrawal")}</TableHead>
<TableHead className='text-end'>{_("Deposit")}</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{transactions.map((transaction) => (
<TableRow key={transaction.name}>
<TableCell>{formatDate(transaction.date)}</TableCell>
<TableCell title={transaction.description} className='max-w-[200px] w-fit overflow-hidden text-ellipsis'>{transaction.description}</TableCell>
<TableCell title={transaction.reference_number} className='max-w-[100px] w-fit overflow-hidden text-ellipsis'>{transaction.reference_number ? transaction.reference_number : "-"}</TableCell>
<TableCell className='text-end font-numeric'>{formatCurrency(transaction.withdrawal, transaction.currency)}</TableCell>
<TableCell className='text-end font-numeric'>{formatCurrency(transaction.deposit, transaction.currency)}</TableCell>
<TableCell className='text-end'>
<Tooltip>
<TooltipTrigger asChild>
<Button variant='link' isIconButton asChild className='text-ink-gray-5 hover:text-black p-0 h-4'>
<a href={`/desk/bank-transaction/${transaction.name}`} target='_blank' rel='noopener noreferrer'>
<ExternalLinkIcon />
</a>
</Button>
</TooltipTrigger>
<TooltipContent>
{_("Open {0} in a new tab", [transaction.name])}
</TooltipContent>
</Tooltip>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant={'outline'} size='md' type='button'>{_("Close")}</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</AlertDescription>
</Alert>
</>
}
export default StatementDetails

View File

@@ -0,0 +1,42 @@
import { BankStatementImportLog } from "@/types/Accounts/BankStatementImportLog"
import { useFrappeGetCall } from "frappe-react-sdk"
export interface GetStatementDetailsResponse {
doc: BankStatementImportLog,
conflicting_transactions: Array<{
name: string,
date: string,
withdrawal: number,
deposit: number,
description: string,
reference_number: string,
currency: string,
}>,
final_transactions: Array<{
date: string,
withdrawal: number,
deposit: number,
description: string,
reference: string,
transaction_type?: string,
debit_credit?: string,
included_fee?: number,
excluded_fee?: number,
party_name?: string,
party_account_number?: string,
party_iban?: string,
}>,
date_format: string,
raw_data: Array<Array<string>>,
currency: string,
}
export const useGetStatementDetails = (id: string) => {
return useFrappeGetCall<{ message: GetStatementDetailsResponse }>("erpnext.accounts.doctype.bank_statement_import_log.bank_statement_import_log.get_statement_details", {
statement_import_id: id,
}, undefined, {
revalidateOnFocus: false
})
}

View File

@@ -0,0 +1,115 @@
import { Badge } from '@/components/ui/badge'
import { Kbd, KbdGroup } from '@/components/ui/kbd'
import { KeyboardMetaKeyIcon } from '@/components/ui/keyboard-keys'
import { SettingsPanelDescription, SettingsPanelTitle, SettingsPanelHeader, SettingsPanelContent } from '@/components/ui/settings-dialog'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import _ from '@/lib/translate'
import { ArrowRightLeftIcon, HistoryIcon, LandmarkIcon, ReceiptIcon, SaveIcon, SettingsIcon, ZapIcon } from 'lucide-react'
const Shortcuts = [
{
shortcut: <KbdGroup><Kbd><KeyboardMetaKeyIcon /></Kbd><Kbd>B</Kbd></KbdGroup>,
action: {
icon: <LandmarkIcon />,
label: _("Bank Entry"),
description: _("Record a bank journal entry for expenses, income or split transactions")
}
},
{
shortcut: <KbdGroup><Kbd><KeyboardMetaKeyIcon /></Kbd><Kbd>P</Kbd></KbdGroup>,
action: {
icon: <ReceiptIcon />,
label: _("Record Payment"),
description: _("Record a payment against a customer or supplier")
}
},
{
shortcut: <KbdGroup><Kbd><KeyboardMetaKeyIcon /></Kbd><Kbd>I</Kbd></KbdGroup>,
action: {
icon: <ArrowRightLeftIcon />,
label: _("Transfer"),
description: _("Record a transfer between two bank accounts")
}
},
{
shortcut: <KbdGroup><Kbd><KeyboardMetaKeyIcon /></Kbd><Kbd>R</Kbd></KbdGroup>,
action: {
icon: <ZapIcon />,
label: _("Accept Matching Rule"),
description: _("Accept the rule for the selected transaction")
}
},
{
shortcut: <KbdGroup><Kbd><KeyboardMetaKeyIcon /></Kbd><Kbd>S</Kbd></KbdGroup>,
action: {
icon: <SaveIcon />,
label: _("Save"),
description: _("Save the currently opened form")
}
},
{
shortcut: <KbdGroup><Kbd><KeyboardMetaKeyIcon /></Kbd><Kbd>Z</Kbd></KbdGroup>,
action: {
icon: <HistoryIcon />,
label: _("Reconciliation History"),
description: _("View all reconciliation actions taken in this session")
}
},
{
shortcut: <KbdGroup><Kbd><KeyboardMetaKeyIcon /></Kbd><Kbd></Kbd><Kbd>G</Kbd></KbdGroup>,
action: {
icon: <SettingsIcon />,
label: _("Settings"),
description: _("Open the settings dialog")
}
}
]
const KeyboardShortcuts = () => {
return (
<>
<SettingsPanelHeader>
<SettingsPanelTitle>{_("Keyboard Shortcuts")}</SettingsPanelTitle>
<SettingsPanelDescription>{_("Get around the system quickly with keyboard shortcuts")}</SettingsPanelDescription>
</SettingsPanelHeader>
<SettingsPanelContent>
<div className='flex flex-col gap-3'>
<p className='text-p-sm text-ink-gray-6'>
{_("Transaction actions work when one or more unreconciled transactions are selected.")}
<br />
{_("To select more than one transaction at a time, press and hold the shift key.")}
</p>
<Table containerClassName='dark:border-outline-gray-2'>
<TableHeader>
<TableRow>
<TableHead>{_("Shortcut")}</TableHead>
<TableHead>{_("Action")}</TableHead>
<TableHead>{_("Description")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Shortcuts.map((shortcut) => (
<TableRow className='hover:bg-surface-gray-2'>
<TableCell>
{shortcut.shortcut}
</TableCell>
<TableCell>
<Badge size='lg' variant='outline'>
{shortcut.action.icon}
{shortcut.action.label}
</Badge>
</TableCell>
<TableCell>
<p className='text-p-sm text-ink-gray-6 text-wrap'>{shortcut.action.description}</p>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</SettingsPanelContent>
</>
)
}
export default KeyboardShortcuts

View File

@@ -0,0 +1,46 @@
import { Button } from '@/components/ui/button'
import { SettingsPanelTitle, SettingsPanelHeader, SettingsPanelDescription, SettingsPanelContent } from '@/components/ui/settings-dialog'
import _ from '@/lib/translate'
import { PlusIcon } from 'lucide-react'
import { useState } from 'react'
import RuleList, { RunRulesButton } from './Rules/RuleList'
import CreateNewRule from '../BankReconciliation/Rules/CreateNewRule'
import EditRule from '../BankReconciliation/Rules/EditRule'
const MatchingRules = () => {
const [selectedRule, setSelectedRule] = useState<string | null>(null)
const [isNewRule, setIsNewRule] = useState(false)
if (isNewRule) {
return <CreateNewRule onCreate={() => setIsNewRule(false)} />
}
if (selectedRule) {
return <EditRule onClose={() => setSelectedRule(null)} ruleID={selectedRule} />
}
return (
<>
<SettingsPanelHeader
actions={
<div className='flex gap-2 items-center'>
<RunRulesButton />
<Button type='button' onClick={() => setIsNewRule(true)}><PlusIcon /> {_("Add Rule")}</Button>
</div>
}
>
<SettingsPanelTitle>{_("Transaction Matching Rules")}</SettingsPanelTitle>
<SettingsPanelDescription>
{_("Set up rules to automatically classify transactions. Drag and drop rules to reorder their priority.")}
</SettingsPanelDescription>
</SettingsPanelHeader>
<SettingsPanelContent>
<RuleList setSelectedRule={setSelectedRule} />
</SettingsPanelContent>
</>
)
}
export default MatchingRules

View File

@@ -0,0 +1,261 @@
import ErrorBanner from "@/components/ui/error-banner"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Separator } from "@/components/ui/separator"
import { SettingsPanelDescription, SettingsPanelHeader, SettingsPanelTitle, SettingsPanelContent } from "@/components/ui/settings-dialog"
import { Switch } from "@/components/ui/switch"
import { useTheme } from "@/components/ui/theme-provider"
import _ from "@/lib/translate"
import { AccountsSettings } from "@/types/Accounts/AccountsSettings"
import { useFrappeGetDoc, useFrappeUpdateDoc } from "frappe-react-sdk"
import { toast } from "sonner"
export const Preferences = () => {
const { data: accountsSettings, mutate, error: fetchError, isLoading } = useFrappeGetDoc<AccountsSettings>("Accounts Settings", "Accounts Settings", undefined, {
revalidateOnFocus: false
})
const { updateDoc, error } = useFrappeUpdateDoc<AccountsSettings>()
const onUpdate = (field: keyof AccountsSettings, value: any) => {
mutate(updateDoc("Accounts Settings", "Accounts Settings", {
[field]: value
}), {
optimisticData: {
...accountsSettings as AccountsSettings,
[field]: value
},
revalidate: false,
}).then(() => {
toast.success(_("Preferences updated"), {
dismissible: true,
duration: 500,
})
})
}
return <>
<SettingsPanelHeader>
<SettingsPanelTitle>{_("Preferences")}</SettingsPanelTitle>
<SettingsPanelDescription>{_("Configure settings for the banking module")}</SettingsPanelDescription>
</SettingsPanelHeader>
<SettingsPanelContent>
<div className='flex flex-col gap-4 w-full'>
{fetchError && <ErrorBanner error={fetchError} />}
{error && <ErrorBanner error={error} />}
<div className="flex flex-col flex-1">
<ThemeSwitcher />
<div className="flex justify-between items-center gap-8 py-3">
<div className="flex flex-col">
<Label htmlFor="transfer_match_days" className="text-p-base text-ink-gray-6">{_("Number of days to match transfers")}</Label>
<p className="text-p-sm text-ink-gray-5">
{_("For example, if set to 4, the system will try to find matching transfer transactions in other banks 4 days before and after the transaction date. This is because transactions can clear on different days on different bank accounts.")}
</p>
</div>
<div className="min-w-40 flex justify-end">
<Select disabled={isLoading} onValueChange={(value) => onUpdate("transfer_match_days", Number(value))} value={accountsSettings?.transfer_match_days?.toString()}>
<SelectTrigger id="transfer_match_days" className="min-w-32">
<SelectValue placeholder={_("Select number of days")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="0">{_("Same day")}</SelectItem>
<SelectItem value="1">{_("Within 1 day")}</SelectItem>
<SelectItem value="2">{_("Within 2 days")}</SelectItem>
<SelectItem value="3">{_("Within 3 days")}</SelectItem>
<SelectItem value="4">{_("Within 4 days")}</SelectItem>
<SelectItem value="5">{_("Within 5 days")}</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<Separator />
<div className="flex justify-between items-center gap-8 py-3">
<div className="flex flex-col">
<Label htmlFor="automatically_run_rules_on_unreconciled_transactions" className="text-p-base text-ink-gray-6">{_("Automatically run rules on unreconciled transactions")}</Label>
<p className="text-p-sm text-ink-gray-5">
{_("This will automatically run transaction matching rules on unreconciled transactions every hour.")}
</p>
</div>
<div className="flex justify-end">
<Switch
id="automatically_run_rules_on_unreconciled_transactions"
className="dark:disabled:bg-surface-gray-2"
disabled={isLoading}
checked={accountsSettings?.automatically_run_rules_on_unreconciled_transactions === 1}
onCheckedChange={(checked) => onUpdate("automatically_run_rules_on_unreconciled_transactions", checked ? 1 : 0)}
/>
</div>
</div>
<Separator />
<div className="flex justify-between items-center gap-8 py-3">
<div className="flex flex-col">
<Label htmlFor="enable_party_matching" className="text-p-base text-ink-gray-6">{_("Enable automatic party matching")}</Label>
<p className="text-p-sm text-ink-gray-5">
{_("The system will attempt to automatically match a party to a bank transaction based on account number or IBAN.")}
</p>
</div>
<div className="flex justify-end">
<Switch
id="enable_party_matching"
className="dark:disabled:bg-surface-gray-2"
disabled={isLoading}
checked={accountsSettings?.enable_party_matching === 1}
onCheckedChange={(checked) => onUpdate("enable_party_matching", checked ? 1 : 0)}
/>
</div>
</div>
<Separator />
<div className="flex justify-between items-center gap-8 py-3">
<div className="flex flex-col">
<Label htmlFor="enable_fuzzy_matching" className="text-p-base text-ink-gray-6">{_("Enable party name/description fuzzy matching")}</Label>
<p className="text-p-sm text-ink-gray-5">
{_("If a party cannot be matched by account number or IBAN, the system will try fuzzy matching using the party name and transaction description.")}
</p>
</div>
<div className="flex justify-end">
<Switch
id="enable_fuzzy_matching"
className="dark:disabled:bg-surface-gray-2"
disabled={accountsSettings?.enable_party_matching !== 1 || isLoading}
checked={accountsSettings?.enable_fuzzy_matching === 1}
onCheckedChange={(checked) => onUpdate("enable_fuzzy_matching", checked ? 1 : 0)}
/>
</div>
</div>
</div>
{/* <DataField
name='transfer_match_days'
label={_("Number of days to match transfers")}
isRequired
inputProps={{
type: 'number',
inputMode: 'numeric',
}}
formDescription={_("For example, if set to 4, the system will try to find matching transactions in other banks 4 days before and after the transaction date. This is because transactions can clear on different days on different bank accounts.")}
/> */}
</div>
</SettingsPanelContent>
</>
}
const ThemeSwitcher = () => {
const { theme, setTheme } = useTheme()
const themeCards: Array<{ value: "Light" | "Dark" | "Automatic", label: string }> = [
{
value: "Light",
label: _("Light"),
},
{
value: "Dark",
label: _("Dark"),
},
{
value: "Automatic",
label: _("System"),
},
]
return <div className="flex flex-col gap-3 pb-3">
<div className="flex flex-col">
<Label className="text-p-base text-ink-gray-6">{_("Theme")}</Label>
<p className="text-p-sm text-ink-gray-5">
{_("Switch between light, dark, or system theme")}
</p>
</div>
<div className="flex gap-3">
{themeCards.map((option) => {
const selected = theme === option.value
return (
<button
key={option.value}
type="button"
onClick={() => setTheme(option.value)}
aria-pressed={selected}
className={`flex-1 basis-0 min-w-0 overflow-hidden rounded-lg border cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-outline-blue-4 ${selected ? "border-outline-gray-5" : "border-outline-gray-modals hover:border-outline-gray-4"}`}
>
{option.value === "Automatic" ? (
<div className="flex w-full min-w-0">
<ThemePreviewWindow theme="light" roundedClass="rounded-tl-[10.5px]" />
<ThemePreviewWindow theme="dark" roundedClass="rounded-tr-[10.5px]" />
</div>
) : (
<ThemePreviewWindow theme={option.value === "Light" ? "light" : "dark"} roundedClass="rounded-t-[10.5px]" />
)}
<div className="flex items-center justify-between px-3 py-2 border-t border-outline-gray-modals">
<div className="text-base text-ink-gray-7">{option.label}</div>
<span className={`rounded-full size-3.5 ${selected ? "border-4 border-outline-gray-5" : "border border-outline-gray-4"}`} />
</div>
</button>
)
})}
</div>
</div>
}
const ThemePreviewWindow = ({ theme, roundedClass }: { theme: "light" | "dark", roundedClass: string }) => {
const isLight = theme === "light"
const frameClass = isLight ? "bg-white border-gray-100" : "bg-gray-900 border-gray-800"
const subtleSurfaceClass = isLight ? "bg-gray-50" : "bg-gray-800"
const mutedLineClass = isLight ? "bg-gray-200" : "bg-gray-700"
const mutedLineStrongClass = isLight ? "bg-gray-300" : "bg-gray-600"
const dividerClass = isLight ? "border-gray-100" : "border-gray-800"
const cardClass = isLight ? "bg-white border-gray-200" : "bg-gray-900 border-gray-700"
return <div className={`flex flex-1 min-w-0 pl-5 pt-3.5 ${isLight ? "bg-surface-gray-2" : "bg-surface-gray-3"} ${roundedClass}`}>
<div className={`w-full rounded-tl-sm border ${frameClass}`}>
<div className={`flex gap-[3px] py-[3px] px-1 border-b ${dividerClass}`}>
<div className="size-1.5 bg-[#FF5F57] rounded-full" />
<div className="size-1.5 bg-[#FEBC2D] rounded-full" />
<div className="size-1.5 bg-[#28C840] rounded-full" />
</div>
<div className="p-1.5">
<div className={`flex items-center gap-1.5 p-1 rounded-sm border ${subtleSurfaceClass} ${dividerClass}`}>
<div className={`h-2 w-8 rounded-full ${mutedLineStrongClass}`} />
<div className={`h-2 w-6 rounded-full ${mutedLineClass}`} />
<div className={`h-2 w-7 rounded-full ml-auto ${mutedLineClass}`} />
</div>
<div className="grid grid-cols-2 gap-1 mt-1.5">
<div className={`rounded-sm border p-1 ${cardClass}`}>
<div className={`h-1.5 w-full rounded-full ${mutedLineStrongClass}`} />
<div className={`h-1.5 w-4/5 rounded-full mt-1 ${mutedLineClass}`} />
<div className={`h-1.5 w-3/5 rounded-full mt-1 ${mutedLineClass}`} />
</div>
<div className={`rounded-sm border p-1 ${cardClass}`}>
<div className="flex items-center justify-between gap-1">
<div className={`h-1.5 w-2/5 rounded-full ${mutedLineStrongClass}`} />
{/* <div className={`h-2.5 w-5 rounded-sm border ${chipClass}`} /> */}
</div>
<div className={`h-1.5 w-full rounded-full mt-1 ${mutedLineClass}`} />
<div className={`h-1.5 w-3/4 rounded-full mt-1 ${mutedLineClass}`} />
</div>
</div>
</div>
</div>
</div>
}

View File

@@ -0,0 +1,314 @@
import { Button } from "@/components/ui/button"
import ErrorBanner from "@/components/ui/error-banner"
import { Skeleton } from "@/components/ui/skeleton"
import { Badge } from "@/components/ui/badge"
import _ from "@/lib/translate"
import { BankTransactionRule } from "@/types/Accounts/BankTransactionRule"
import { FrappeConfig, FrappeContext, useFrappeGetCall, useFrappeGetDocList, useFrappePostCall } from "frappe-react-sdk"
import { ArrowDownRight, ArrowDownUp, ArrowUpRight, MoreVertical, Trash2, GripVertical, Play, RefreshCw, ZapIcon, CalendarSyncIcon } from "lucide-react"
import { useContext, useState } from "react"
import { toast } from "sonner"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator, DropdownMenuCheckboxItem } from "@/components/ui/dropdown-menu"
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragEndEvent,
} from '@dnd-kit/core'
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import {
useSortable,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"
import { cn } from "@/lib/utils"
const useGetRuleList = () => {
return useFrappeGetDocList<BankTransactionRule>("Bank Transaction Rule", {
fields: ["name", "rule_name", "rule_description", "transaction_type", "priority"],
orderBy: {
field: 'priority',
order: 'asc'
},
limit: 100
})
}
export const RunRulesButton = () => {
const { data } = useGetRuleList()
const { call: runRuleEvaluation, loading: isRunningRules } = useFrappePostCall('erpnext.accounts.doctype.bank_transaction_rule.bank_transaction_rule.run_rule_evaluation')
const handleRunRules = async (forceEvaluate: boolean = false) => {
try {
await runRuleEvaluation({
force_evaluate: forceEvaluate
})
toast.success(forceEvaluate ? _("Rules evaluation started") : _("Rules evaluation completed"))
} catch (error) {
toast.error(_("Failed to run rules evaluation"))
console.error("Error running rules evaluation:", error)
}
}
if (!data || data.length === 0) {
return null
}
return <DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" disabled={isRunningRules}>
{isRunningRules ? (
<RefreshCw className="animate-spin" />
) : (
<Play />
)}
{isRunningRules ? _("Running...") : _("Run Rules")}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => handleRunRules(false)} disabled={isRunningRules} title={_("Run rules on unreconciled transactions that haven't been evaluated yet")}>
<Play />
{_("Run on new transactions")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleRunRules(true)} disabled={isRunningRules} title={_("Force re-evaluate all unreconciled transactions, even if they were previously evaluated")}>
<RefreshCw />
{_("Force evaluate all")}
</DropdownMenuItem>
<DropdownMenuSeparator />
<AutoRunRuleItem />
</DropdownMenuContent>
</DropdownMenu>
}
const AutoRunRuleItem = () => {
const { db } = useContext(FrappeContext) as FrappeConfig
const { data: accountsSetting, mutate: setAutomaticallyRunRulesOnUnreconciledTransactions } = useFrappeGetCall("frappe.client.get_single_value", {
"doctype": "Accounts Settings",
"field": "automatically_run_rules_on_unreconciled_transactions"
})
const automaticallyRunRulesOnUnreconciledTransactions = accountsSetting?.message ? true : false
const onAutoClassifyTransactions = (checked: boolean) => {
toast.promise(db.setValue("Accounts Settings", "Accounts Settings", "automatically_run_rules_on_unreconciled_transactions", checked ? 1 : 0).then(() => {
setAutomaticallyRunRulesOnUnreconciledTransactions({
message: {
automatically_run_rules_on_unreconciled_transactions: checked ? 1 : 0,
}
}, {
revalidate: false
})
}), {
loading: _("Updating..."),
success: checked ? _("Scheduled job enabled. Transactions will be auto classified.") : _("Scheduled job disabled. Transactions will not be auto classified."),
error: _("Failed to update auto classify transactions settings")
})
}
return <DropdownMenuCheckboxItem
checked={automaticallyRunRulesOnUnreconciledTransactions}
onCheckedChange={onAutoClassifyTransactions}>
<CalendarSyncIcon />
{_("Run rules automatically")}
</DropdownMenuCheckboxItem>
}
const RuleList = ({ setSelectedRule }: { setSelectedRule: (rule: string) => void }) => {
const { data, error, isLoading, mutate } = useGetRuleList()
const { db } = useContext(FrappeContext) as FrappeConfig
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
)
const onDeleteRule = (ruleID: string) => {
toast.promise(db.deleteDoc("Bank Transaction Rule", ruleID).then(() => {
mutate()
}), {
loading: _("Deleting rule..."),
success: _("Rule deleted."),
error: _("Failed to delete rule.")
})
}
const handleDragEnd = async (event: DragEndEvent) => {
const { active, over } = event
if (active.id !== over?.id && data) {
const oldIndex = data.findIndex((rule) => rule.name === active.id)
const newIndex = data.findIndex((rule) => rule.name === over?.id)
const newData = arrayMove(data, oldIndex, newIndex)
// Update priorities based on new order
const updatePromises = newData.map((rule, index) => {
const newPriority = index + 1
if (rule.priority !== newPriority) {
return db.setValue("Bank Transaction Rule", rule.name, "priority", newPriority)
}
return Promise.resolve()
})
try {
await Promise.all(updatePromises)
toast.success(_("Rule priorities updated"))
mutate() // Refresh the data
} catch (error) {
toast.error(_("Failed to update rule priorities"))
console.error("Error updating priorities:", error)
}
}
}
return (
<>
<div className="overflow-y-auto">
{isLoading && <div className="flex flex-col gap-2">
<Skeleton className="w-full h-10" />
<Skeleton className="w-full h-10" />
<Skeleton className="w-full h-10" />
<Skeleton className="w-full h-10" />
<Skeleton className="w-full h-10" />
</div>}
{error && <ErrorBanner error={error} />}
{data && data.length === 0 && <Empty className="h-96">
<EmptyMedia>
<ZapIcon />
</EmptyMedia>
<EmptyHeader>
<EmptyTitle>{_("No rules setup yet")}</EmptyTitle>
<EmptyDescription>{_("Configure rules to save time when reconciling transactions.")}</EmptyDescription>
</EmptyHeader>
</Empty>}
{data && data.length > 0 && (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={data.map(rule => rule.name)}
strategy={verticalListSortingStrategy}
>
<ul className="space-2 divide-y divide-outline-gray-modals">
{data?.map((rule) => (
<SortableRuleItem
key={rule.name}
rule={rule}
setSelectedRule={setSelectedRule}
onDeleteRule={onDeleteRule}
/>
))}
</ul>
</SortableContext>
</DndContext>
)}
</div>
</>
)
}
const SortableRuleItem = ({
rule,
setSelectedRule,
onDeleteRule
}: {
rule: BankTransactionRule
setSelectedRule: (rule: string) => void
onDeleteRule: (ruleID: string) => void
}) => {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: rule.name })
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
}
const [isDropdownOpen, setIsDropdownOpen] = useState(false)
return (
<li ref={setNodeRef} style={style}>
<div className={cn("flex justify-between items-center py-2 my-0.5 h-full hover:bg-surface-gray-1 pe-2 rounded", isDropdownOpen && "bg-surface-gray-1")}>
<div className="flex items-center gap-2">
<div
{...attributes}
{...listeners}
className="cursor-grab active:cursor-grabbing p-1 rounded"
title={_("Drag to reorder")}
>
<GripVertical className="w-4 h-4 text-ink-gray-5" />
</div>
<Badge theme="gray" className="font-numeric tabular-nums">
{rule.priority}
</Badge>
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<Button
variant='link'
size='sm'
className="p-0 h-fit text-start cursor-pointer no-underline hover:underline"
onClick={() => setSelectedRule(rule.name)}>
{rule.rule_name}
</Button>
<div title={rule.transaction_type === "Any" ? _("Applies to withdrawals and deposits") : rule.transaction_type === "Withdrawal" ? _("Applies to withdrawals") : _("Applies to deposits")}>
{rule.transaction_type === "Any" ? <ArrowDownUp className="text-ink-gray-5 w-4 h-4" /> : rule.transaction_type === "Withdrawal" ? <ArrowUpRight className="text-ink-red-3 w-5 h-5" /> : <ArrowDownRight className="text-ink-green-3 w-5 h-5" />}
</div>
</div>
<span className="text-sm text-ink-gray-5">
{rule.rule_description}
</span>
</div>
</div>
<div className="flex items-center gap-2 h-full justify-center">
<DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
<DropdownMenuTrigger asChild>
<Button variant='ghost' isIconButton className="hover:bg-transparent">
<MoreVertical />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onDeleteRule(rule.name)}>
<Trash2 />
{_("Delete")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</li>
)
}
export default RuleList

View File

@@ -0,0 +1,95 @@
import { Button } from '@/components/ui/button'
import { Dialog, DialogTrigger } from '@/components/ui/dialog'
import {
SettingsDialog,
SettingsPanel,
SettingsPanels,
SettingsTabGroup,
SettingsTabItem,
SettingsTabs,
} from '@/components/ui/settings-dialog'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import _ from '@/lib/translate'
import { KeyboardIcon, SettingsIcon, SlidersVerticalIcon, ZapIcon } from 'lucide-react'
import { useState } from 'react'
import { Preferences } from './Preferences'
import MatchingRules from './MatchingRules'
import KeyboardShortcuts from './KeyboardShortcuts'
import { useHotkeys } from 'react-hotkeys-hook'
const Settings = () => {
const [isOpen, setIsOpen] = useState(false)
useHotkeys('shift+meta+g', () => {
setIsOpen(x => !x)
}, {
enabled: true,
preventDefault: true,
enableOnFormTags: false
})
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<Tooltip>
<TooltipTrigger asChild>
<DialogTrigger asChild>
<Button variant={'outline'} isIconButton size='md'>
<SettingsIcon />
</Button>
</DialogTrigger>
</TooltipTrigger>
<TooltipContent>
{_("Settings")}
</TooltipContent>
</Tooltip>
<SettingsDialog defaultValue="preferences" onClose={() => setIsOpen(false)}>
<SettingsTabs>
<SettingsTabGroup header={_("Settings")}>
<SettingsTabItem
icon={<SlidersVerticalIcon />}
label={_("Preferences")}
value="preferences"
/>
<SettingsTabItem
icon={<ZapIcon />}
label={_("Matching Rules")}
value="rules"
/>
{/* <SettingsTabItem
icon={<LandmarkIcon />}
label={_("Bank Accounts")}
value="bank-accounts"
/>
<SettingsTabItem
icon={<ListIcon />}
label={_("Masters")}
value="masters"
/> */}
<SettingsTabItem
icon={<KeyboardIcon />}
label={_("Keyboard Shortcuts")}
value="keyboard-shortcuts"
/>
</SettingsTabGroup>
</SettingsTabs>
<SettingsPanels>
<SettingsPanel value="preferences">
<Preferences />
</SettingsPanel>
<SettingsPanel value="rules">
<MatchingRules />
</SettingsPanel>
<SettingsPanel value="bank-accounts" />
<SettingsPanel value="masters" />
<SettingsPanel value="keyboard-shortcuts">
<KeyboardShortcuts />
</SettingsPanel>
</SettingsPanels>
</SettingsDialog>
</Dialog >
)
}
export default Settings

View File

@@ -0,0 +1,196 @@
import * as React from "react"
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
function AlertDialog({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
}
function AlertDialogTrigger({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
)
}
function AlertDialogPortal({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
)
}
function AlertDialogOverlay({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
return (
<AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black-200 dark:bg-black-700",
className
)}
{...props}
/>
)
}
function AlertDialogContent({
className,
size = "default",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
size?: "default" | "sm"
}) {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
data-slot="alert-dialog-content"
data-size={size}
className={cn(
"bg-surface-modal shadow-xl rounded-xl data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 p-6 duration-200 data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
)
}
function AlertDialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-header"
className={cn(
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-start sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
className
)}
{...props}
/>
)
}
function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
className
)}
{...props}
/>
)
}
function AlertDialogTitle({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn(
"text-2xl leading-6 text-ink-gray-8 font-semibold sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
className
)}
{...props}
/>
)
}
function AlertDialogDescription({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn("text-ink-gray-7 text-p-base", className)}
{...props}
/>
)
}
function AlertDialogMedia({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-media"
className={cn(
"bg-surface-gray-1 mb-2 inline-flex size-16 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
className
)}
{...props}
/>
)
}
function AlertDialogAction({
className,
variant = "solid",
size = "md",
theme = "red",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
Pick<React.ComponentProps<typeof Button>, "variant" | "size" | "theme">) {
return (
<Button variant={variant} size={size} theme={theme} asChild>
<AlertDialogPrimitive.Action
data-slot="alert-dialog-action"
className={cn(className)}
{...props}
/>
</Button>
)
}
function AlertDialogCancel({
className,
variant = "outline",
size = "md",
theme = "gray",
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
Pick<React.ComponentProps<typeof Button>, "variant" | "size" | "theme">) {
return (
<Button variant={variant} size={size} asChild>
<AlertDialogPrimitive.Cancel
data-slot="alert-dialog-cancel"
className={cn(className)}
{...props}
/>
</Button>
)
}
export {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogOverlay,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
}

View File

@@ -0,0 +1,104 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3.5 text-base grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-1 [&>svg]:text-current",
{
variants: {
variant: {
subtle: "bg-surface-white",
outline: "border border-outline-gray-3",
},
theme: {
gray: "text-ink-gray-8",
blue: "text-ink-blue-3",
green: "text-ink-green-3",
red: "text-ink-red-3",
amber: "text-ink-amber-3",
}
},
compoundVariants: [
// Subtle alerts
{
theme: "gray",
variant: "subtle",
className: "bg-surface-gray-2 border-outline-gray-1"
},
{
theme: "blue",
variant: "subtle",
className: "bg-surface-blue-2 border-surface-blue-2"
},
{
theme: "green",
variant: "subtle",
className: "bg-surface-green-2 border-surface-green-2"
},
{
theme: "red",
variant: "subtle",
className: "bg-surface-red-2 border-surface-red-2"
},
{
theme: "amber",
variant: "subtle",
className: "bg-surface-amber-2 border-surface-amber-2"
}
],
defaultVariants: {
variant: "subtle",
theme: "gray",
},
}
)
export type AlertProps = React.ComponentProps<"div"> & VariantProps<typeof alertVariants>
function Alert({
className,
variant,
theme,
...props
}: AlertProps) {
return (
<div
data-slot="alert"
role="alert"
className={cn(alertVariants({ variant, theme }), className)}
{...props}
/>
)
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-title"
className={cn(
"col-start-2 min-h-4 text-ink-gray-8 font-medium text-p-base",
className
)}
{...props}
/>
)
}
function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-description"
className={cn(
"text-ink-gray-6 col-start-2 grid justify-items-start gap-1 text-p-base",
className
)}
{...props}
/>
)
}
export { Alert, AlertTitle, AlertDescription }

View File

@@ -0,0 +1,188 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center justify-center select-none rounded-full whitespace-nowrap gap-1 w-fit shrink-0 [&>svg]:pointer-events-none transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
solid: "",
subtle: "",
outline: "bg-transparent border",
ghost: "bg-transparent",
},
size: {
sm: 'h-4 text-xs px-1.5 [&>svg]:size-2.5',
md: 'h-5 text-xs px-1.5 [&>svg]:size-3',
lg: 'h-6 text-sm px-2 [&>svg]:size-3',
},
theme: {
gray: "",
blue: "",
green: "",
red: "",
orange: "",
violet: "",
}
},
compoundVariants: [
// Solid badges
{
variant: "solid",
theme: "gray",
className: "text-ink-white bg-surface-gray-7 [a&]:hover:bg-surface-gray-8"
},
{
variant: "solid",
theme: "blue",
className: "text-ink-blue-1 bg-surface-blue-5 [a&]:hover:bg-surface-blue-6"
},
{
variant: "solid",
theme: "green",
className: "text-ink-green-1 bg-surface-green-5 [a&]:hover:bg-surface-green-6"
},
{
variant: "solid",
theme: "orange",
className: "text-ink-amber-1 bg-surface-amber-5 [a&]:hover:bg-surface-amber-6"
},
{
variant: "solid",
theme: "red",
className: "text-ink-red-1 bg-surface-red-5 [a&]:hover:bg-surface-red-6"
},
{
variant: "solid",
theme: "violet",
className: "text-ink-violet-1 bg-surface-violet-5 [a&]:hover:bg-surface-violet-6"
},
// Subtle badge
{
variant: "subtle",
theme: "gray",
className: "text-ink-gray-6 bg-surface-gray-2 [a&]:hover:bg-surface-gray-3"
},
{
variant: "subtle",
theme: "blue",
className: "text-ink-blue-4 bg-surface-blue-2 [a&]:hover:bg-surface-blue-3"
},
{
variant: "subtle",
theme: "green",
className: "text-ink-green-4 bg-surface-green-2 [a&]:hover:bg-surface-green-3"
},
{
variant: "subtle",
theme: "orange",
className: "text-ink-amber-4 bg-surface-amber-2 [a&]:hover:bg-surface-amber-3"
},
{
variant: "subtle",
theme: "red",
className: "text-ink-red-4 bg-surface-red-2 [a&]:hover:bg-surface-red-3"
},
{
variant: "subtle",
theme: "violet",
className: "text-ink-violet-4 bg-surface-violet-2 [a&]:hover:bg-surface-violet-3"
},
// Outline badge
{
variant: "outline",
theme: "gray",
className: "text-ink-gray-6 border-outline-gray-2 [a&]:hover:bg-surface-gray-2"
},
{
variant: "outline",
theme: "blue",
className: "text-ink-blue-4 border-outline-blue-2 [a&]:hover:bg-surface-blue-2"
},
{
variant: "outline",
theme: "green",
className: "text-ink-green-4 border-outline-green-2 [a&]:hover:bg-surface-green-2"
},
{
variant: "outline",
theme: "orange",
className: "text-ink-amber-4 border-outline-amber-2 [a&]:hover:bg-surface-amber-2"
},
{
variant: "outline",
theme: "red",
className: "text-ink-red-4 border-outline-red-2 [a&]:hover:bg-surface-red-2"
},
{
variant: "outline",
theme: "violet",
className: "text-ink-violet-4 border-outline-violet-2 [a&]:hover:bg-surface-violet-2"
},
// Ghost badge
{
variant: "ghost",
theme: "gray",
className: "text-ink-gray-6"
},
{
variant: "ghost",
theme: "blue",
className: "text-ink-blue-4"
},
{
variant: "ghost",
theme: "green",
className: "text-ink-green-4"
},
{
variant: "ghost",
theme: "orange",
className: "text-ink-amber-4"
},
{
variant: "ghost",
theme: "red",
className: "text-ink-red-4"
},
{
variant: "ghost",
theme: "violet",
className: "text-ink-violet-4"
}
],
defaultVariants: {
variant: "subtle",
size: "md",
theme: "gray",
},
}
)
function Badge({
className,
variant = "subtle",
size = "md",
theme = "gray",
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span"
return (
<Comp
data-slot="badge"
data-variant={variant}
data-size={size}
data-theme={theme}
className={cn(badgeVariants({ variant, size, theme }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }

View File

@@ -0,0 +1,109 @@
import * as React from "react"
import { MoreHorizontal } from "lucide-react"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
return (
<ol
data-slot="breadcrumb-list"
className={cn(
"text-ink-gray-5 flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
className
)}
{...props}
/>
)
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-item"
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
)
}
function BreadcrumbLink({
asChild,
className,
...props
}: React.ComponentProps<"a"> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "a"
return (
<Comp
data-slot="breadcrumb-link"
className={cn("text-ink-gray-5 font-medium text-lg hover:text-ink-gray-7 active:text-ink-gray-7 transition-colors", className)}
{...props}
/>
)
}
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn("text-ink-gray-8 text-lg font-medium text-balance tracking-wide", className)}
{...props}
/>
)
}
function BreadcrumbSeparator({
children,
className,
...props
}: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <span className="text-ink-gray-4 text-base">/</span>}
</li>
)
}
function BreadcrumbEllipsis({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
className={cn("flex size-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="size-4" />
<span className="sr-only">More</span>
</span>
)
}
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
}

View File

@@ -0,0 +1,263 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap transition-all disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none aria-invalid:shadow-focus-red aria-invalid:border-outline-red-3",
{
variants: {
variant: {
solid: "text-ink-white",
subtle: "",
ghost: "bg-transparent",
outline: "bg-surface-white border",
link: "bg-transparent underline-offset-4 underline",
},
size: {
sm: "h-7 text-base px-2 rounded [&_svg:not([class*='size-'])]:size-4",
md: "h-8 text-base font-medium px-2.5 rounded [&_svg:not([class*='size-'])]:size-4.5",
lg: "h-10 text-lg font-medium px-3 rounded-md [&_svg:not([class*='size-'])]:size-5",
xl: "h-11.5 text-xl font-medium px-3.5 rounded-lg [&_svg:not([class*='size-'])]:size-6",
"2xl": "h-13 text-2xl font-medium px-3.5 rounded-xl [&_svg:not([class*='size-'])]:size-6",
},
theme: {
gray: "focus-visible:shadow-focus-gray",
blue: "focus-visible:shadow-focus-blue",
green: "focus-visible:shadow-focus-green",
red: "focus-visible:shadow-focus-red",
amber: "focus-visible:shadow-focus-amber",
violet: "focus-visible:shadow-focus-violet",
},
isIconButton: {
true: "px-0",
false: ""
}
},
compoundVariants: [
// Icon only buttons - Sizes
{
isIconButton: true,
size: "sm",
className: "size-7"
},
{
isIconButton: true,
size: "md",
className: "size-8"
},
{
isIconButton: true,
size: "lg",
className: "size-10"
},
{
isIconButton: true,
size: "xl",
className: "size-11.5"
},
{
isIconButton: true,
size: "2xl",
className: "size-13"
},
// Solid buttons
{
variant: "solid",
theme: "gray",
className: "bg-surface-gray-7 hover:bg-surface-gray-6 active:bg-surface-gray-5 disabled:bg-surface-gray-2 disabled:text-ink-gray-4"
},
{
variant: "solid",
theme: "blue",
className: "bg-surface-blue-5 text-ink-blue-1 hover:bg-surface-blue-6 active:bg-surface-blue-7 disabled:bg-surface-blue-2 disabled:text-ink-blue-2"
},
{
variant: "solid",
theme: "green",
className: "bg-surface-green-5 text-ink-green-1 hover:bg-surface-green-6 active:bg-surface-green-7 disabled:bg-surface-green-2 disabled:text-ink-green-2"
},
{
variant: "solid",
theme: "red",
className: "bg-surface-red-5 text-ink-red-1 hover:bg-surface-red-6 active:bg-surface-red-7 disabled:bg-surface-red-2 disabled:text-ink-red-2"
},
{
variant: "solid",
theme: "violet",
className: "bg-surface-violet-5 text-ink-violet-1 hover:bg-surface-violet-6 active:bg-surface-violet-7 disabled:bg-surface-violet-2 disabled:text-ink-violet-2"
},
{
variant: "solid",
theme: "amber",
className: "bg-surface-amber-5 text-ink-amber-1 hover:bg-surface-amber-6 active:bg-surface-amber-7 disabled:bg-surface-amber-2 disabled:text-ink-amber-2"
},
// Subtle Buttons
{
variant: "subtle",
theme: "gray",
className: "text-ink-gray-7 bg-surface-gray-2 hover:bg-surface-gray-3 active:bg-surface-gray-4 disabled:bg-surface-gray-2 disabled:text-ink-gray-4"
},
{
variant: "subtle",
theme: "blue",
className: "text-ink-blue-4 bg-surface-blue-2 hover:bg-surface-blue-3 active:bg-surface-blue-4 disabled:bg-surface-blue-2 disabled:text-ink-blue-2"
},
{
variant: "subtle",
theme: "green",
className: "text-ink-green-4 bg-surface-green-2 hover:bg-surface-green-3 active:bg-surface-green-4 disabled:bg-surface-green-2 disabled:text-ink-green-2"
},
{
variant: "subtle",
theme: "red",
className: "text-ink-red-4 bg-surface-red-2 hover:bg-surface-red-3 active:bg-surface-red-4 disabled:bg-surface-red-2 disabled:text-ink-red-2"
},
{
variant: "subtle",
theme: "violet",
className: "text-ink-violet-4 bg-surface-violet-2 hover:bg-surface-violet-3 active:bg-surface-violet-4 disabled:bg-surface-violet-2 disabled:text-ink-violet-2"
},
{
variant: "subtle",
theme: "amber",
className: "text-ink-amber-4 bg-surface-amber-2 hover:bg-surface-amber-3 active:bg-surface-amber-4 disabled:bg-surface-amber-2 disabled:text-ink-amber-2"
},
// Outline buttons
{
variant: "outline",
theme: "gray",
className:
"text-ink-gray-7 border-outline-gray-2 hover:border-outline-gray-3 active:border-outline-gray-4 active:bg-surface-gray-4 disabled:bg-surface-gray-2 disabled:text-ink-gray-4 disabled:border-outline-gray-2"
},
{
variant: "outline",
theme: "blue",
className:
"text-ink-blue-4 border-outline-blue-2 hover:border-outline-blue-3 active:border-outline-blue-4 active:bg-surface-blue-4 disabled:bg-surface-blue-2 disabled:text-ink-blue-2 disabled:border-outline-blue-2"
},
{
variant: "outline",
theme: "green",
className:
"text-ink-green-4 border-outline-green-2 hover:border-outline-green-3 active:border-outline-green-4 active:bg-surface-green-4 disabled:bg-surface-green-2 disabled:text-ink-green-2 disabled:border-outline-green-2"
},
{
variant: "outline",
theme: "red",
className:
"text-ink-red-4 border-outline-red-2 hover:border-outline-red-3 active:border-outline-red-4 active:bg-surface-red-4 disabled:bg-surface-red-2 disabled:text-ink-red-2 disabled:border-outline-red-2"
},
{
variant: "outline",
theme: "violet",
className: "text-ink-violet-4 border-outline-violet-2 hover:border-outline-violet-3 active:border-outline-violet-4 active:bg-surface-violet-4 disabled:bg-surface-violet-2 disabled:text-ink-violet-2 disabled:border-outline-violet-2"
},
{
variant: "outline",
theme: "amber",
className: "text-ink-amber-4 border-outline-amber-2 hover:border-outline-amber-3 active:border-outline-amber-4 active:bg-surface-amber-4 disabled:bg-surface-amber-2 disabled:text-ink-amber-2 disabled:border-outline-amber-2"
},
// Ghost buttons
{
variant: "ghost",
theme: "gray",
className:
"text-ink-gray-7 hover:bg-surface-gray-3 active:bg-surface-gray-4 disabled:text-ink-gray-4"
},
{
variant: "ghost",
theme: "blue",
className:
"text-ink-blue-4 hover:bg-surface-blue-3 active:bg-surface-blue-4 disabled:text-ink-blue-2"
},
{
variant: "ghost",
theme: "green",
className:
"text-ink-green-4 hover:bg-surface-green-3 active:bg-surface-green-4 disabled:text-ink-green-2"
},
{
variant: "ghost",
theme: "red",
className:
"text-ink-red-4 hover:bg-surface-red-3 active:bg-surface-red-4 disabled:text-ink-red-2"
},
{
variant: "ghost",
theme: "violet",
className: "text-ink-violet-4 hover:bg-surface-violet-3 active:bg-surface-violet-4 disabled:text-ink-violet-2"
},
{
variant: "ghost",
theme: "amber",
className: "text-ink-amber-4 hover:bg-surface-amber-3 active:bg-surface-amber-4 disabled:text-ink-amber-2"
},
//Link buttons
{
variant: "link",
theme: "gray",
className: "text-ink-gray-8 hover:text-ink-gray-8 active:text-ink-gray-8 disabled:text-ink-gray-4"
},
{
variant: "link",
theme: "blue",
className: "text-ink-blue-3 hover:text-ink-blue-4 active:text-ink-blue-4 disabled:text-ink-blue-link"
},
{
variant: "link",
theme: "green",
className: "text-ink-green-3 hover:text-ink-green-4 active:text-ink-green-4 disabled:text-ink-green-2"
},
{
variant: "link",
theme: "red",
className: "text-ink-red-3 hover:text-ink-red-4 active:text-red-4 disabled:text-ink-red-2"
},
{
variant: "link",
theme: "violet",
className: "text-ink-violet-3 hover:text-ink-violet-4 active:text-ink-violet-4 disabled:text-ink-violet-2"
},
{
variant: "link",
theme: "amber",
className: "text-ink-amber-3 hover:text-ink-amber-4 active:text-ink-amber-4 disabled:text-ink-amber-2"
}
],
defaultVariants: {
variant: "solid",
size: "sm",
theme: "gray",
},
}
)
function Button({
className,
variant = "solid",
size = "sm",
theme = "gray",
isIconButton = false,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
data-theme={theme}
className={cn(buttonVariants({ variant, size, theme, className, isIconButton }))}
{...props}
/>
)
}
export { Button, buttonVariants }

View File

@@ -0,0 +1,218 @@
import * as React from "react"
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "lucide-react"
import {
DayPicker,
getDefaultClassNames,
type DayButton,
} from "react-day-picker"
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
formatters,
components,
...props
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
}) {
const defaultClassNames = getDefaultClassNames()
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
"bg-surface-modal group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className
)}
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString("default", { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
"flex gap-4 flex-col md:flex-row relative",
defaultClassNames.months
),
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
nav: cn(
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
defaultClassNames.nav
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
defaultClassNames.button_previous
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
defaultClassNames.button_next
),
month_caption: cn(
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
defaultClassNames.month_caption
),
dropdowns: cn(
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
defaultClassNames.dropdowns
),
dropdown_root: cn(
"relative has-focus:border-outline-gray-1 border border-outline-gray-2 shadow-xs has-focus:ring-outline-gray-1/50 has-focus:ring-[3px] rounded-md",
defaultClassNames.dropdown_root
),
dropdown: cn(
"absolute bg-surface-modal inset-0 opacity-0",
defaultClassNames.dropdown
),
caption_label: cn(
"select-none font-medium",
captionLayout === "label"
? "text-sm"
: "rounded-md ps-2 pe-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-ink-gray-5 [&>svg]:size-3.5",
defaultClassNames.caption_label
),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"text-ink-gray-5 rounded-md flex-1 font-normal text-[0.8rem] select-none",
defaultClassNames.weekday
),
week: cn("flex w-full mt-2", defaultClassNames.week),
week_number_header: cn(
"select-none w-(--cell-size)",
defaultClassNames.week_number_header
),
week_number: cn(
"text-[0.8rem] select-none text-ink-gray-5",
defaultClassNames.week_number
),
day: cn(
"relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-e-md group/day aspect-square select-none",
props.showWeekNumber
? "[&:nth-child(2)[data-selected=true]_button]:rounded-s-md"
: "[&:first-child[data-selected=true]_button]:rounded-s-md",
defaultClassNames.day
),
range_start: cn(
"rounded-s-md bg-surface-gray-1",
defaultClassNames.range_start
),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn("rounded-e-md bg-surface-gray-1", defaultClassNames.range_end),
today: cn(
"bg-surface-gray-1 text-ink-gray-8 rounded-md data-[selected=true]:rounded-none",
defaultClassNames.today
),
outside: cn(
"text-ink-gray-5 aria-selected:text-ink-gray-5",
defaultClassNames.outside
),
disabled: cn(
"text-ink-gray-5 opacity-50",
defaultClassNames.disabled
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
)
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
)
}
if (orientation === "right") {
return (
<ChevronRightIcon
className={cn("size-4", className)}
{...props}
/>
)
}
return (
<ChevronDownIcon className={cn("size-4", className)} {...props} />
)
},
DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
)
},
...components,
}}
{...props}
/>
)
}
function CalendarDayButton({
className,
day,
modifiers,
...props
}: React.ComponentProps<typeof DayButton>) {
const defaultClassNames = getDefaultClassNames()
const ref = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus()
}, [modifiers.focused])
return (
<Button
ref={ref}
variant="ghost"
isIconButton
data-day={day.date.toLocaleDateString()}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
"data-[selected-single=true]:bg-surface-gray-7 data-[selected-single=true]:text-ink-white data-[range-middle=true]:bg-surface-gray-1 data-[range-middle=true]:text-ink-gray-8 data-[range-start=true]:bg-surface-gray-7 data-[range-start=true]:text-ink-white data-[range-end=true]:bg-surface-gray-7 data-[range-end=true]:text-ink-white group-data-[focused=true]/day:border-outline-gray-1 group-data-[focused=true]/day:ring-outline-gray-1/50 dark:hover:text-ink-gray-8 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-e-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-s-md [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day,
className
)}
{...props}
/>
)
}
export { Calendar, CalendarDayButton }

View File

@@ -0,0 +1,92 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-surface-cards text-ink-gray-8 flex flex-col gap-6 rounded-xl border py-6 shadow-xs",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-ink-gray-5 text-sm", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

View File

@@ -0,0 +1,44 @@
import * as React from "react"
import { CheckIcon } from "lucide-react"
import { Checkbox as CheckboxPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Checkbox({
className,
size = "md",
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root> & { size?: "sm" | "md" }) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer border data-[state=checked]:text-ink-white shrink-0 transition-shadow outline-none align-middle",
"rounded-[4px]",
"border-ink-gray-4 data-[state=checked]:bg-ink-gray-8 data-[state=checked]:border-ink-gray-8",
// Hover state
"hover:border-ink-gray-5 hover:shadow-checkbox-hover hover:data-[state=checked]:bg-ink-gray-7 hover:data-[state=checked]:border-ink-gray-7",
// Active state
"active:border-ink-gray-6 active:data-[state=checked]:bg-ink-gray-6 active:data-[state=checked]:border-ink-gray-6",
// Focus state
"focus-visible:border-ink-gray-8 focus-visible:shadow-focus-gray focus-visible:data-[state=checked]:bg-ink-gray-8 focus-visible:data-[state=checked]:border-ink-gray-8",
// Disabled state
"disabled:border-ink-gray-3 disabled:bg-surface-gray-1 disabled:cursor-not-allowed disabled:data-[state=checked]:bg-surface-gray-3 disabled:data-[state=checked]:border-surface-gray-3 disabled:text-ink-gray-4",
// Invalid state
"aria-invalid:border-red-500",
size === "sm" ? "size-3.5" : "size-4",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none"
>
<CheckIcon className={size === 'sm' ? "size-2.5" : "size-3"} />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }

View File

@@ -0,0 +1,183 @@
import * as React from "react"
import { Command as CommandPrimitive } from "cmdk"
import { SearchIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
function Command({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
"bg-surface-modal flex h-full w-full flex-col overflow-hidden rounded-md",
className
)}
{...props}
/>
)
}
function CommandDialog({
title = "Command Palette",
description = "Search for a command to run...",
children,
className,
showCloseButton = true,
...props
}: React.ComponentProps<typeof Dialog> & {
title?: string
description?: string
className?: string
showCloseButton?: boolean
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent
className={cn("overflow-hidden p-0", className)}
showCloseButton={showCloseButton}
>
<Command className="[&_[cmdk-group-heading]]:text-ink-gray-4 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
function CommandInput({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div
data-slot="command-input-wrapper"
className="flex items-center gap-2 m-1.5 h-8 rounded px-2.5 py-2 border border-transparent transition-all bg-surface-gray-2 not-focus-within:hover:bg-surface-gray-3 text-ink-gray-7 focus-within:bg-surface-white focus-within:border-outline-gray-4 focus-within:shadow-focus-gray"
>
<SearchIcon className="size-4 shrink-0 text-ink-gray-4" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"flex w-full bg-transparent outline-hidden text-base placeholder:text-ink-gray-4",
className
)}
{...props}
/>
</div>
)
}
function CommandList({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn(
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
className
)}
{...props}
/>
)
}
function CommandEmpty({
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return (
<CommandPrimitive.Empty
data-slot="command-empty"
className="py-6 text-center text-sm"
{...props}
/>
)
}
function CommandGroup({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"text-ink-gray-6 [&_[cmdk-group-heading]]:text-ink-gray-4 overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-sm [&_[cmdk-group-heading]]:font-medium",
className
)}
{...props}
/>
)
}
function CommandSeparator({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("bg-outline-gray-modals mx-0.5 h-px my-1", className)}
{...props}
/>
)
}
function CommandItem({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"py-1.5 px-2 flex cursor-default text-ink-gray-6 items-center gap-2 rounded text-base relative outline-hidden select-none",
"data-[selected=true]:bg-surface-gray-2 [&_svg:not([class*='text-'])]:text-ink-gray-6 data-[disabled=true]:pointer-events-none data-[disabled=true]:text-ink-gray-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function CommandShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="command-shortcut"
className={cn(
"text-ink-gray-5 ms-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
}
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View File

@@ -0,0 +1,156 @@
import * as React from "react"
import { XIcon } from "lucide-react"
import { Dialog as DialogPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black-200 dark:bg-black-700",
className
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-surface-modal shadow-xl rounded-xl data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 p-6 duration-200 outline-none sm:max-w-lg max-h-[90vh] overflow-y-auto",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="data-[state=open]:bg-surface-gray-1 data-[state=open]:text-ink-gray-8 absolute top-4 ltr:right-4 rtl:left-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon className="w-4 h-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 sm:text-start", className)}
{...props}
/>
)
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean
}) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close asChild>
<Button variant="outline">Close</Button>
</DialogPrimitive.Close>
)}
</div>
)
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-2xl leading-6 text-ink-gray-8 font-semibold", className)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-ink-gray-7 text-p-base", className)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}

View File

@@ -0,0 +1,20 @@
import * as React from "react"
import { Direction } from "radix-ui"
function DirectionProvider({
dir,
direction,
children,
}: React.ComponentProps<typeof Direction.DirectionProvider> & {
direction?: React.ComponentProps<typeof Direction.DirectionProvider>["dir"]
}) {
return (
<Direction.DirectionProvider dir={direction ?? dir}>
{children}
</Direction.DirectionProvider>
)
}
const useDirection = Direction.useDirection
export { DirectionProvider, useDirection }

View File

@@ -0,0 +1,262 @@
import * as React from "react"
import { CheckIcon, ChevronRightIcon } from "lucide-react"
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function DropdownMenu({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
}
function DropdownMenuPortal({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
)
}
function DropdownMenuTrigger({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
{...props}
/>
)
}
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
"bg-surface-modal min-w-32 rounded-lg p-1 shadow-xl",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
)
}
function DropdownMenuGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
)
}
const BASE_ITEM_STYLES = `outline-hidden select-none relative flex cursor-default items-center
gap-2 rounded px-2 py-1.5 text-base text-ink-gray-6 data-[variant=destructive]:text-ink-red-3
data-[variant=destructive]:*:[svg]:text-ink-red-3! [&_svg:not([class*='text-'])]:text-ink-gray-6 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0
data-disabled:pointer-events-none data-disabled:text-ink-gray-3 data-disabled:*:[svg]:text-ink-gray-3! focus:bg-surface-gray-2 data-inset:ps-8`
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
BASE_ITEM_STYLES,
className
)}
{...props}
/>
)
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
BASE_ITEM_STYLES,
className
)}
checked={checked}
{...props}
>
{children}
<span className="pointer-events-none flex size-4 ms-2 px-2 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
</DropdownMenuPrimitive.CheckboxItem>
)
}
function DropdownMenuRadioGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return (
<DropdownMenuPrimitive.RadioGroup
data-slot="dropdown-menu-radio-group"
{...props}
/>
)
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
BASE_ITEM_STYLES,
className
)}
{...props}
>
{children}
<span className="pointer-events-none flex size-4 ps-2 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
</DropdownMenuPrimitive.RadioItem>
)
}
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"px-2 py-1.5 text-sm font-medium text-ink-gray-4 data-inset:ps-8",
className
)}
{...props}
/>
)
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-outline-gray-modals my-1 h-px mx-0.5", className)}
{...props}
/>
)
}
function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn(
"text-ink-gray-5 ms-auto text-xs tabular-nums",
className
)}
{...props}
/>
)
}
function DropdownMenuSub({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
BASE_ITEM_STYLES,
"data-[state=open]:bg-surface-gray-3",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ms-auto cn-rtl-flip size-4" />
</DropdownMenuPrimitive.SubTrigger>
)
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-surface-modal rounded-lg p-1 shadow-xl min-w-32 text-ink-gray-6 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden",
className
)}
{...props}
/>
)
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
}

View File

@@ -0,0 +1,85 @@
import { cn } from "@/lib/utils"
function Empty({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty"
className={cn(
"flex min-w-0 min-h-64 flex-1 flex-col items-center justify-center gap-3 rounded-lg p-6 text-center text-balance",
className
)}
{...props}
/>
)
}
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-header"
className={cn(
"flex flex-col items-center gap-1 text-center",
className
)}
{...props}
/>
)
}
function EmptyMedia({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-icon"
className={cn("flex justify-center items-center shrink-0 [&_svg]:pointer-events-none [&_svg]:shrink-0 bg-transparent size-7.5 [&_svg:not([class*='size-'])]:size-7.5 [&_svg:not([class*='text-'])]:text-ink-gray-5", className)}
{...props}
/>
)
}
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-title"
className={cn("text-lg font-medium text-ink-gray-7", className)}
{...props}
/>
)
}
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
data-slot="empty-description"
className={cn(
"text-center text-p-base text-ink-gray-6 [&>a:hover]:text-ink-gray-7 [&>a]:underline [&>a]:underline-offset-4",
className
)}
{...props}
/>
)
}
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-content"
className={cn(
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
className
)}
{...props}
/>
)
}
export {
Empty,
EmptyHeader,
EmptyTitle,
EmptyDescription,
EmptyContent,
EmptyMedia,
}

View File

@@ -0,0 +1,51 @@
import { getErrorMessages } from '@/lib/frappe'
import { FrappeError } from 'frappe-react-sdk'
import { Alert, AlertDescription, AlertProps, AlertTitle } from '@/components/ui/alert'
import { AlertCircle } from 'lucide-react'
import MarkdownRenderer from '@/components/ui/markdown'
import _ from '@/lib/translate'
import { useMemo } from 'react'
type ErrorBannerProps = AlertProps & {
error?: FrappeError | null,
overrideHeading?: string,
}
interface ParsedErrorMessage {
message: string,
title?: string,
indicator?: string,
}
const parseHeading = (message?: ParsedErrorMessage) => {
if (message?.title === 'Message' || message?.title === 'Error') return "There was an error."
return message?.title
}
const ErrorBanner = ({ error, overrideHeading, ...props }: ErrorBannerProps) => {
//exc_type: "ValidationError" or "PermissionError" etc
// exc: With entire traceback - useful for reporting maybe
// httpStatus and httpStatusText - not needed
// _server_messages: Array of messages - useful for showing to user
// console.log(JSON.parse(error?._server_messages!))
const messages = useMemo(() => {
return getErrorMessages(error)
}, [error])
return (
<Alert theme={messages[0]?.indicator === 'yellow' ? 'amber' : "red"} {...props}>
<AlertCircle />
<AlertTitle>{overrideHeading ?? parseHeading(messages[0])}</AlertTitle>
<AlertDescription>
{messages.map((m, i) => {
return <MarkdownRenderer content={m.message} key={i} />
})}
</AlertDescription>
</Alert>
)
}
export default ErrorBanner

View File

@@ -0,0 +1,289 @@
import _ from '@/lib/translate'
import { Dispatch, SetStateAction, useCallback } from 'react'
import { Accept, useDropzone } from 'react-dropzone'
import { cn } from '@/lib/utils'
import { formatBytes, getFileExtension } from '@/lib/file'
import { Button } from './button'
import { Trash2Icon } from 'lucide-react'
type Props = {
files: File[],
setFiles?: Dispatch<SetStateAction<File[]>>
accept?: Accept,
multiple?: boolean
onDrop?: (acceptedFiles: File[]) => void,
onUpdate?: VoidFunction
className?: string
}
export const FileDropzone = ({ files, setFiles, accept, multiple = true, onDrop, className, onUpdate }: Props) => {
const onFileDrop = useCallback((acceptedFiles: File[]) => {
// Do something with the files
if (multiple) {
setFiles?.((prev) => [...prev, ...acceptedFiles])
} else {
setFiles?.(acceptedFiles)
}
onDrop?.(acceptedFiles)
onUpdate?.()
}, [setFiles, onDrop, multiple, onUpdate])
const { getRootProps, getInputProps } = useDropzone({ onDrop: onFileDrop, accept, multiple })
return (
<div {...getRootProps()} className={cn('border border-outline-gray-2 border-dashed p-4 rounded bg-surface-gray-1 focus-within:bg-surface-gray-2 hover:bg-surface-gray-2 hover:border-outline-gray-3 focus-within:border-outline-gray-3 focus-within:outline-none', className)}>
<input {...getInputProps()} />
{files.length === 0 ? <p className='text-sm text-ink-gray-5 text-center h-8 flex items-center justify-center'>{multiple ? _("Drop some files here, or click to select files") : _("Drop a file here, or click to select a file")}</p> : null}
<div className='flex flex-col gap-4'>
{files.map(f => <div key={f.name} className='flex justify-between items-center'>
<div className='flex items-center gap-2'>
<FileTypeIcon fileType={getFileExtension(f.name)} size='sm' />
<div className='flex flex-col gap-0.5'>
<span className='text-ink-gray-7 text-sm'>{f.name}</span>
<span className='text-ink-gray-5 text-xs'>{formatBytes(f.size)}</span>
</div>
</div>
<Button type='button' variant='ghost' isIconButton
className='text-ink-gray-5 hover:text-ink-gray-8 hover:bg-transparent'
onClick={(e) => {
e.stopPropagation()
setFiles?.(files.filter(file => file.name !== f.name))
onUpdate?.()
}}>
<Trash2Icon className='w-4 h-4' />
</Button>
</div>)}
</div>
</div>
)
}
interface FileTypeIconProps {
fileType: string
size?: 'sm' | 'md' | 'lg' | 'xl'
className?: string
showBackground?: boolean
}
const sizeClasses = {
sm: 'h-8 w-8',
md: 'h-10 w-10',
lg: 'h-12 w-12',
xl: 'h-16 w-16'
}
const iconSizeClasses = {
sm: 'h-5 w-5',
md: 'h-6 w-6',
lg: 'h-8 w-8',
xl: 'h-10 w-10'
}
// Special sizing for PowerPoint icon due to different viewBox
const pptIconSizeClasses = {
sm: 'h-3.5 w-3.5',
md: 'h-4 w-4',
lg: 'h-5 w-5',
xl: 'h-6 w-6'
}
export const FileTypeIcon = ({
fileType,
size = 'md',
className,
showBackground = true
}: FileTypeIconProps) => {
const containerClass = cn(sizeClasses[size], className)
const RenderIcon = ({ className }: { className?: string }) => {
switch (fileType.toLowerCase()) {
case 'pdf':
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" className={cn("text-white", iconSizeClasses[size], className)}>
<path d="M7 22.9c.1-.6.5-1 .9-1.4.5-.5 1.1-.8 1.8-1.2.7-.4 1.4-.7 2.1-1 .1 0 .2-.1.2-.2.6-1.2 1.2-2.4 1.7-3.6.3-.7.5-1.4.8-2.1v-.1c-.3-.7-.6-1.5-.7-2.3-.2-.8-.2-1.6-.1-2.4.1-.5.4-.9.8-1.3.1-.1.3-.1.5-.1h.8c.2 0 .4.1.5.3.3.2.5.5.7.8.2.4.2.8.3 1.2 0 1.2-.2 2.3-.4 3.4-.1.4-.2.7-.3 1.1v.1c.6 1.1 1.4 2.1 2.2 3 .1.1.1.1.3.1 1.1-.2 2.2-.2 3.2-.2.6 0 1.3.1 1.9.4.3.2.6.4.8.7.1.2.2.4.2.6v.7c0 .2-.1.4-.3.5-.2.2-.4.5-.8.5-.2 0-.5.1-.7.1-1.6.1-2.9-.4-4.2-1.3-.2-.2-.5-.4-.7-.6-.1 0-.1-.1-.2-.1-.6.1-1.2.2-1.8.4-.8.2-1.6.5-2.4.7-.1 0-.1.1-.2.1-.5.9-1.1 1.8-1.7 2.6-.5.6-1.1 1.2-1.7 1.7-.3.2-.7.4-1.1.5h-.8c-.2 0-.3 0-.5-.1-.5-.2-.9-.6-1-1.1-.1 0-.1-.2-.1-.4zm8.8-7c-.3.8-.7 1.6-1 2.4l2.4-.6c-.5-.6-1-1.3-1.4-1.8zm4.3 2.6c.6.4 1.3.7 2 .9.3.1.5 0 .7-.1.2-.1.3-.4.1-.5 0-.1-.1-.1-.2-.1-.2-.1-.5-.1-.8-.2-.6-.1-1.2-.1-1.8 0zm-9.4 2.8s-.1 0 0 0c-.6.3-1.2.7-1.7 1.1-.3.2-.5.5-.7.8v.2c.1.1.1.1.2.1.3-.2.5-.4.7-.5.6-.5 1-1.1 1.5-1.7zM15 11.2c.1 0 .1 0 0 0 .2-.6.3-1.2.3-1.7 0-.3 0-.6-.1-.9 0-.1-.1-.1-.2-.1s-.1.1-.2.1c-.2.3-.2.6-.2 1 0 .3 0 .5.1.8.2.2.2.5.3.8z" fill="currentColor" />
</svg>
)
case 'doc':
case 'docx':
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" className={cn("text-white", iconSizeClasses[size], className)}>
<path d="M26 11.4V8.8c0-.4-.3-.8-.8-.8h-7.3V6.2h-1.4c-.2 0-.3.1-.5.1-.7.1-1.4.3-2.1.4-.7.1-1.4.2-2 .4-.7.1-1.4.2-2.2.4-.8.1-1.5.2-2.2.3-.5.1-1 .2-1.4.2H6v15.9c.8.1 1.6.3 2.4.4.8.1 1.7.3 2.5.4.8.1 1.6.3 2.4.4.8.1 1.7.3 2.5.5.3.1.7.1 1 .1h.9V24c0-.1 0-.1.1-.1h7.3c.1 0 .3 0 .4-.1.2 0 .3-.1.3-.3 0-.2.1-.3.1-.5V11.4c.1.1.1.1.1 0zm-11 1.5l-.9 3.9c-.2.7-.3 1.4-.5 2.2 0 .1-.1.1-.1.1-.2.1-.4 0-.6 0h-.6c-.1 0-.1 0-.1-.1-.1-.6-.3-1.3-.4-1.9-.2-.8-.3-1.6-.5-2.4 0 .2-.1.4-.1.6l-.6 3c0 .2-.1.5-.1.7 0 .1 0 .1-.1.1-.4 0-.8-.1-1.2-.1-.1 0-.1 0-.1-.1-.3-1.6-.6-3.2-1-4.9-.1-.3-.1-.7-.2-1v-.1h1.2c.2 1.4.5 2.8.7 4.3 0-.2.1-.4.1-.6.3-1.2.5-2.5.8-3.7 0-.1 0-.1.1-.1h1c.2 0 .2 0 .3.2.3 1.4.6 2.8.9 4.3v.1c.1-.8.3-1.6.4-2.4.1-.7.3-1.5.4-2.2 0 0 0-.1.1-.1.4 0 .8 0 1.3-.1h.1c-.2 0-.3.2-.3.3zm10.3-4.1s0 .1 0 0v14.5h-7.5v-1.8h5.9v-.9H18c-.1 0-.1 0-.1-.1v-.9c0-.1 0-.1.1-.1h5.8v-.9h-5.9v-1.1h5.8v-.9h-5.9v-1h5.8c.1 0 .1 0 .1-.1v-.7c0-.1 0-.1-.1-.1H18c-.1 0-.1 0-.1-.1v-1h5.9v-.9h-5.7c-.1 0-.1 0-.1-.1v-.9c0-.1 0-.1.1-.1h5.7v-.9h-5.9v-1.2h5.8c.1 0 .1 0 .1-.1v-.7c0-.1 0-.1-.1-.1h-5.9V9c0-.1 0-.1.1-.1h7.3c.1-.2.1-.2.1-.1z" fill="currentColor" />
</svg>
)
case 'xls':
case 'xlsx':
case 'csv':
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" className={cn("text-white", iconSizeClasses[size], className)}>
<path d="M26 9.3v13.6c0 .1-.1.2-.1.3-.2.3-.5.5-.8.5h-7.7v2c-.3-.1-.7-.1-1-.2-.7-.1-1.5-.3-2.2-.4-.8-.1-1.6-.3-2.4-.4-.8-.1-1.6-.3-2.4-.4-.7-.1-1.5-.3-2.2-.4-.4-.1-.7-.1-1.1-.2V9c.1 0 .3-.1.4-.1.7-.5 1.5-.7 2.3-.8.7-.1 1.4-.3 2-.4.6-.1 1.3-.2 1.9-.4.7-.1 1.5-.3 2.2-.4.8-.1 1.5-.3 2.3-.4h.1v1.9h7.8c.4 0 .8.3.9.7v.2zm-.8-.1h-7.9v1.2H20v1.7h-2.7v.6H20v1.7h-2.7v.6H20v1.7h-2.7v.7h2.8v1.7h-2.8v.6H20v1.7h-2.7v1.2h7.9V9.2zM14.7 20.7s0-.1-.1-.1c-.7-1.4-1.5-2.8-2.2-4.2v-.2c.7-1.4 1.4-2.7 2.2-4.1V12h-.1c-.2 0-.5 0-.7.1-.3 0-.6 0-1 .1-.1 0-.1 0-.1.1-.3.6-.5 1.1-.8 1.7-.2.5-.4.9-.6 1.4-.1-.2-.1-.5-.2-.7-.3-.7-.6-1.5-.9-2.2-.1-.2-.1-.2-.3-.2-.4 0-.8.1-1.2.1h-.4v.1c.1.2.2.5.3.7l1.5 3v.1c-.6 1.2-1.3 2.4-1.9 3.6 0 .1-.1.1-.1.2h.6c.4 0 .7.1 1.1.1.1 0 .1 0 .1-.1.3-.6.6-1.2.9-1.9.1-.3.3-.6.4-.9 0-.1 0-.2.1-.3v.1c.1.2.1.4.2.5.4.8.7 1.6 1.1 2.5.1.1.1.2.3.2.5 0 1 .1 1.5.1.1.3.2.3.3.3z" fill="currentColor" />
<path d="M23.9 10.4v1.7h-3.1v-1.7h3.1zm-3.1 11.2v-1.7h3.1v1.7h-3.1zm0-4.7v-1.7h3.1v1.7h-3.1zm3.1-4.1v1.7h-3.1v-1.7h3.1zm0 4.8v1.7h-3.1v-1.7h3.1z" fill="currentColor" />
</svg>
)
case 'ppt':
case 'pptx':
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 116.03" className={cn("text-white", pptIconSizeClasses[size], className)}>
<g>
<path d="M0.38,12.11L69.16,0.09L69.69,0v0.54v114.96v0.53l-0.53-0.09L0.38,104.63L0,104.57v-0.38V12.55v-0.38L0.38,12.11 L0.38,12.11z M76.29,17.01h43.79c0.77,0,1.47,0.32,1.98,0.82c0.51,0.51,0.82,1.21,0.82,1.98v76.75c0,0.78-0.32,1.5-0.84,2.01 s-1.23,0.84-2.01,0.84H76.29h-0.45v-0.45v-9.16v-0.45h0.45h33.62v-6.15H76.29h-0.45v-0.45v-7.17V75.1h0.45h33.62v-6.15H76.29h-0.45 v-0.45v-8.49v-0.88l0.71,0.51c1.32,0.94,2.79,1.68,4.36,2.18c1.52,0.48,3.14,0.74,4.82,0.74c4.38,0,8.34-1.78,11.21-4.64 c2.82-2.82,4.59-6.7,4.64-11H85.83h-0.45v-0.45V30.86c-1.56,0.03-3.06,0.29-4.47,0.74c-1.57,0.5-3.04,1.24-4.36,2.18l-0.71,0.51 v-0.88V17.46v-0.45H76.29L76.29,17.01z M99.26,32.75c-2.76-2.77-6.54-4.52-10.73-4.65v15.48h15.36 C103.79,39.35,102.04,35.53,99.26,32.75L99.26,32.75z M30.91,80.41V63.97v-0.45h0.45h6.22c2.41,0,4.56-0.35,6.45-1.05 c1.87-0.7,3.49-1.75,4.86-3.15c1.37-1.4,2.39-3.04,3.08-4.91c0.69-1.88,1.03-4,1.03-6.37c0-1.61-0.16-3.12-0.48-4.55 c-0.32-1.42-0.79-2.76-1.43-4.01c-0.63-1.25-1.4-2.36-2.29-3.32c-0.89-0.96-1.91-1.78-3.06-2.45c-2.31-1.35-4.97-2.03-7.98-2.03 H22.07v48.75H30.91L30.91,80.41z M37.76,55.2h-6.39h-0.45v-0.45V40.43v-0.45h0.45h6.51l0.01,0c0.95,0.01,1.81,0.21,2.57,0.59 c0.76,0.38,1.41,0.95,1.96,1.71h0c0.54,0.74,0.95,1.6,1.21,2.58c0.27,0.97,0.4,2.05,0.4,3.24c0,1.1-0.13,2.08-0.39,2.94h0 c-0.27,0.88-0.67,1.63-1.21,2.26c-0.54,0.63-1.21,1.11-2,1.43C39.65,55.05,38.76,55.2,37.76,55.2L37.76,55.2z" fill="currentColor" />
</g>
</svg>
)
case 'video':
case 'mp4':
case 'mov':
case 'mkv':
case 'avi':
case 'webm':
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" className={cn("text-white", iconSizeClasses[size], className)}>
<path d="M16 4c-6.6 0-12 5.4-12 12s5.4 12 12 12 12-5.4 12-12S22.6 4 16 4zm-2 16.5V9.5l8 5.5-8 5.5z" fill="currentColor" />
</svg>
)
case 'audio':
case 'mp3':
case 'wav':
case 'ogg':
case 'flac':
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" className={cn("text-white", iconSizeClasses[size], className)}>
<path d="M16 4c-6.6 0-12 5.4-12 12s5.4 12 12 12 12-5.4 12-12S22.6 4 16 4zm-2 16.5V9.5l8 5.5-8 5.5z" fill="currentColor" />
</svg>
)
case 'image':
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
case 'webp':
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" className={cn("text-white", iconSizeClasses[size], className)}>
<path d="M26 4H6c-1.1 0-2 .9-2 2v20c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM6 26V6h20v20H6z" fill="currentColor" />
<path d="M10 12c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm12 8H10l4-6 3 4 2-3 7 5z" fill="currentColor" />
</svg>
)
case 'zip':
case 'rar':
case '7z':
case 'tar':
case 'gz':
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" className={cn("text-white", iconSizeClasses[size], className)}>
<path d="M26 4H6c-1.1 0-2 .9-2 2v20c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM6 26V6h20v20H6z" fill="currentColor" />
<path d="M10 8h12v2H10V8zm0 4h12v2H10v-2zm0 4h12v2H10v-2z" fill="currentColor" />
</svg>
)
default:
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" className={cn("text-white", iconSizeClasses[size], className)}>
<path d="M18 22a2 2 0 0 0 2-2V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12zM13 4l5 5h-5V4zM7 8h3v2H7V8zm0 4h10v2H7v-2zm0 4h10v2H7v-2z" fill="currentColor" />
</svg>
)
}
}
const getBackgroundColor = () => {
switch (fileType.toLowerCase()) {
case 'pdf':
return 'bg-red-700'
case 'doc':
case 'docx':
return 'bg-[#1A5CBD]'
case 'xls':
case 'xlsx':
case 'csv':
return 'bg-green-700'
case 'ppt':
case 'pptx':
return 'bg-[#ED6C47]'
case 'video':
case 'mp4':
case 'mov':
case 'mkv':
case 'avi':
case 'webm':
return 'bg-purple-600'
case 'audio':
case 'mp3':
case 'wav':
case 'ogg':
case 'flac':
return 'bg-purple-600'
case 'image':
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
case 'webp':
return 'bg-blue-600'
case 'zip':
case 'rar':
case '7z':
case 'tar':
case 'gz':
return 'bg-yellow-600'
default:
return 'bg-gray-500'
}
}
const getTextColor = () => {
switch (fileType.toLowerCase()) {
case 'pdf':
return 'text-red-700'
case 'doc':
case 'docx':
return 'text-[#1A5CBD]'
case 'xls':
case 'xlsx':
case 'csv':
return 'text-green-700 dark:text-green-500'
case 'ppt':
case 'pptx':
return 'text-[#ED6C47]'
case 'video':
case 'mp4':
case 'mov':
case 'mkv':
case 'avi':
case 'webm':
return 'text-purple-600'
case 'audio':
case 'mp3':
case 'wav':
case 'ogg':
case 'flac':
return 'text-purple-600'
case 'image':
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
case 'webp':
return 'text-blue-600'
case 'zip':
case 'rar':
case '7z':
case 'tar':
case 'gz':
return 'text-yellow-600'
default:
return 'text-gray-50'
}
}
if (showBackground) {
return (
<div className={cn("rounded-md flex items-center justify-center", getBackgroundColor(), containerClass)}>
<RenderIcon />
</div>
)
}
return (
<div className={cn("flex items-center justify-center")}>
<RenderIcon className={getTextColor()} />
</div>
)
}

View File

@@ -0,0 +1,383 @@
import { FieldValues, RegisterOptions, useFormContext } from "react-hook-form"
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, FormRequiredIndicator, useFormField } from "@/components/ui/form"
import _ from "@/lib/translate"
import { Input } from "./input"
import { ComponentProps, FocusEventHandler, useCallback, useState } from "react"
import { parseDate } from "chrono-node"
import { formatDate, getUserDateFormat, toDate } from "@/lib/date"
import { Popover, PopoverContent, PopoverTrigger } from "./popover"
import { Button } from "./button"
import { CalendarIcon } from "lucide-react"
import { Calendar } from "./calendar"
import dayjs from "dayjs"
import { Textarea } from "./textarea"
import AccountsDropdown, { AccountsDropdownProps } from "../common/AccountsDropdown"
import PartyTypeDropdown, { PartyTypeDropdownProps } from "../common/PartyTypeDropdown"
import CurrencyInput from "react-currency-input-field"
import { getSystemDefault } from "@/lib/frappe"
import { getCurrencySymbol } from "@/lib/currency"
import { getCurrencyFormatInfo } from "@/lib/numbers"
import LinkFieldCombobox, { LinkFieldComboboxProps } from "../common/LinkFieldCombobox"
import { Select, SelectContent, SelectTrigger, SelectValue } from "./select"
import { InputGroup, InputGroupAddon } from "./input-group"
interface FormElementProps {
name: string,
rules?: Omit<RegisterOptions<FieldValues, string>, "disabled" | "valueAsNumber" | "valueAsDate" | "setValueAs">,
label: string,
isRequired?: boolean,
disabled?: boolean,
formDescription?: string,
hideLabel?: boolean,
readOnly?: boolean,
}
interface DataFieldProps extends FormElementProps {
inputProps?: Omit<ComponentProps<"input">, "value" | "onChange" | "onBlur" | "name" | "ref">
}
export const DataField = ({ name, rules, label, isRequired, formDescription, inputProps, hideLabel, disabled, readOnly }: DataFieldProps) => {
const { control } = useFormContext()
return <FormField
control={control}
disabled={disabled}
name={name}
rules={rules}
render={({ field }) => (
<FormItem className='flex flex-col'>
<FormLabel className={hideLabel ? 'sr-only' : ''}>{label}{isRequired && <FormRequiredIndicator />}</FormLabel>
<FormControl>
<Input {...field} maxLength={140} aria-readonly={readOnly} readOnly={readOnly} {...inputProps} />
</FormControl>
{formDescription && <FormDescription>{formDescription}</FormDescription>}
<FormMessage />
</FormItem>
)}
/>
}
interface SelectFieldProps extends FormElementProps {
children: React.ReactNode
}
export const SelectFormField = ({ name, rules, label, isRequired, formDescription, hideLabel, children, disabled, readOnly }: SelectFieldProps) => {
const { control } = useFormContext()
return <FormField
control={control}
name={name}
disabled={disabled}
rules={rules}
render={({ field }) => (
<FormItem>
<FormLabel className={hideLabel ? 'sr-only' : ''}>{label}{isRequired && <FormRequiredIndicator />}</FormLabel>
<FormControl>
<Select onValueChange={field.onChange} value={field.value} disabled={disabled || readOnly} aria-readonly={readOnly}>
<FormControl>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
{children}
</SelectContent>
</Select>
</FormControl>
{formDescription && <FormDescription>{formDescription}</FormDescription>}
<FormMessage />
</FormItem>
)}
/>
}
interface DateFieldProps extends FormElementProps {
inputProps?: Omit<ComponentProps<"input">, "value" | "onChange" | "onBlur" | "name" | "ref">
}
export const DateField = ({ name, rules, label, isRequired, formDescription, inputProps, hideLabel, disabled }: DateFieldProps) => {
const { control } = useFormContext()
const DatePicker = ({ field }: { field: FieldValues }) => {
const userDateFormat = getUserDateFormat()
const [open, setOpen] = useState(false)
const [value, setValue] = useState<string | undefined>(field.value ? formatDate(field.value) : undefined)
const date = field.value ? toDate(field.value) : undefined
return <div className="relative flex gap-2">
<FormControl>
<Input className="pe-10"
name={field.name}
onBlur={() => {
setValue(formatDate(field.value))
field.onBlur()
}}
placeholder={userDateFormat}
value={value}
onChange={(e) => {
setValue(e.target.value)
if (e.target.value) {
// On change in value, try computing date usning standard formats first
const dateObj = toDate(e.target.value, userDateFormat)
// If we find a valid date, use it
if (dateObj && !isNaN(dateObj.getTime())) {
field.onChange(formatDate(dateObj, "YYYY-MM-DD"))
} else {
// If not, try parsing using chrono-node for things like "1st July 2025"
const date = parseDate(e.target.value)
if (date) {
field.onChange(formatDate(date, "YYYY-MM-DD"))
}
}
} else {
field.onChange("")
}
}}
onKeyDown={(e) => {
if (e.key === "ArrowDown") {
e.preventDefault()
setOpen(true)
}
}}
maxLength={140}
{...inputProps} />
</FormControl>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id="date-picker-button"
variant="ghost"
className="absolute top-1/2 ltr:right-2 rtl:left-2 size-6 -translate-y-1/2"
>
<CalendarIcon className="size-3.5" />
<span className="sr-only">{_("Select date")}</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto overflow-hidden p-0" align="center">
<Calendar
mode="single"
selected={date}
fixedWeeks
endMonth={dayjs().add(1, "year").toDate()}
captionLayout="dropdown"
defaultMonth={date}
onSelect={(date) => {
setValue(formatDate(date))
field.onChange(formatDate(date, "YYYY-MM-DD"))
setOpen(false)
}}
/>
</PopoverContent>
</Popover>
</div>
}
return <FormField
control={control}
name={name}
disabled={disabled}
rules={rules}
render={({ field }) => (
<FormItem className='flex flex-col'>
<FormLabel className={hideLabel ? 'sr-only' : ''}>{label}{isRequired && <FormRequiredIndicator />}</FormLabel>
<DatePicker field={field} />
{formDescription && <FormDescription>{formDescription}</FormDescription>}
<FormMessage />
</FormItem>
)}
/>
}
interface SmallTextFieldProps extends FormElementProps {
inputProps?: Omit<ComponentProps<"textarea">, "value" | "onChange" | "onBlur" | "name" | "ref">
}
export const SmallTextField = ({ name, rules, label, isRequired, formDescription, inputProps, hideLabel, disabled, readOnly }: SmallTextFieldProps) => {
const { control } = useFormContext()
return <FormField
control={control}
name={name}
disabled={disabled}
rules={rules}
render={({ field }) => (
<FormItem className='flex flex-col'>
<FormLabel className={hideLabel ? 'sr-only' : ''}>{label}{isRequired && <FormRequiredIndicator />}</FormLabel>
<FormControl>
<Textarea {...field} {...inputProps} readOnly={readOnly} aria-readonly={readOnly} />
</FormControl>
{formDescription && <FormDescription>{formDescription}</FormDescription>}
<FormMessage />
</FormItem>
)}
/>
}
interface AccountFormFieldProps extends Omit<AccountsDropdownProps, 'value' | 'onChange'>, FormElementProps {
}
export const AccountFormField = (props: AccountFormFieldProps) => {
const { control } = useFormContext()
return <FormField
control={control}
disabled={props.disabled}
name={props.name}
rules={props.rules}
render={({ field }) => (
<FormItem className='flex flex-col'>
<FormLabel className={props.hideLabel ? 'sr-only' : ''}>{props.label}{props.isRequired && <FormRequiredIndicator />}</FormLabel>
<AccountsDropdown {...props} value={field.value} onChange={field.onChange} useInForm readOnly={props.readOnly} />
{props.formDescription && <FormDescription>{props.formDescription}</FormDescription>}
<FormMessage />
</FormItem>
)}
/>
}
interface PartyTypeFormField extends FormElementProps {
inputProps?: Omit<PartyTypeDropdownProps, 'value' | 'onChange'>
}
export const PartyTypeFormField = ({ name, rules, label, isRequired, formDescription, hideLabel, inputProps, disabled, readOnly }: PartyTypeFormField) => {
const { control } = useFormContext()
return <FormField
control={control}
disabled={disabled}
name={name}
rules={rules}
render={({ field }) => (
<FormItem className='flex flex-col'>
<FormLabel className={hideLabel ? 'sr-only' : ''}>{label}{isRequired && <FormRequiredIndicator />}</FormLabel>
<PartyTypeDropdown {...inputProps} value={field.value} onChange={field.onChange} useInForm readOnly={readOnly} />
{formDescription && <FormDescription>{formDescription}</FormDescription>}
<FormMessage />
</FormItem>
)}
/>
}
interface CurrencyFormFieldProps extends FormElementProps {
currency?: string,
style?: React.CSSProperties,
leftSlot?: React.ReactNode,
}
export const CurrencyFormField = ({ name, rules, label, isRequired, formDescription, hideLabel, currency, disabled, readOnly, style = {}, leftSlot }: CurrencyFormFieldProps) => {
const { control } = useFormContext()
const defaultCurrency = getSystemDefault("currency")
const currencySymbol = getCurrencySymbol(currency ?? defaultCurrency)
const CurrencyField = ({ field }: { field: FieldValues }) => {
const onFocus: FocusEventHandler<HTMLInputElement> = useCallback((e) => {
// When the input is focused, select the text
// A short timeout is needed so that the input selects the text after the focus event
setTimeout(() => {
// Check if the input is focused - do not select text if the input is not focused
if (e.target.contains(document.activeElement)) {
e.target.select()
}
}, 100)
}, [])
const { formItemId } = useFormField()
// Get the correct separators for the currency
const formatInfo = getCurrencyFormatInfo(currency ?? defaultCurrency)
const groupSeparator = formatInfo.group_sep || ","
const decimalSeparator = formatInfo.decimal_str || "."
return <CurrencyInput
ref={field.ref}
name={field.name}
style={{
textAlign: 'right',
...style
}}
id={formItemId}
onBlur={field.onBlur}
disabled={field.disabled}
readOnly={readOnly}
aria-readonly={readOnly}
onFocus={onFocus}
groupSeparator={groupSeparator}
decimalSeparator={decimalSeparator}
placeholder={`${currencySymbol} 0${decimalSeparator}00`}
decimalsLimit={2}
value={field.value}
maxLength={12}
decimalScale={2}
prefix={currencySymbol + " "}
onValueChange={(v, _n, values) => {
// If the input ends with a decimal or a decimal with trailing zeroes, store the string since we need the user to be able to type the decimals.
// When the user eventually types the decimals or blurs out, the value is formatted anyway.
// Otherwise store the float value
// Check if the value ends with a decimal or a decimal with trailing zeroes
const isDecimal = v?.endsWith(decimalSeparator) || v?.endsWith(decimalSeparator + '0')
const newValue = isDecimal ? v : values?.float ?? ''
field.onChange(newValue)
}}
customInput={Input}
/>
}
return <FormField
control={control}
disabled={disabled}
name={name}
rules={rules}
render={({ field }) => (
<FormItem className='flex flex-col'>
<FormLabel className={hideLabel ? 'sr-only' : ''}>{label}{isRequired && <FormRequiredIndicator />}</FormLabel>
<FormControl>
<InputGroup>
{leftSlot && <InputGroupAddon>{leftSlot}</InputGroupAddon>}
<CurrencyField field={field} />
</InputGroup>
</FormControl>
{formDescription && <FormDescription>{formDescription}</FormDescription>}
<FormMessage />
</FormItem>
)}
/>
}
interface LinkFormFieldProps extends FormElementProps, Omit<LinkFieldComboboxProps, 'value' | 'onChange'> {
}
export const LinkFormField = ({ name, rules, label, isRequired, formDescription, hideLabel, disabled, readOnly, ...inputProps }: LinkFormFieldProps) => {
const { control } = useFormContext()
return <FormField
control={control}
name={name}
disabled={disabled}
rules={rules}
render={({ field }) => (
<FormItem className='flex flex-col'>
<FormLabel className={hideLabel ? 'sr-only' : ''}>{label}{isRequired && <FormRequiredIndicator />}</FormLabel>
<LinkFieldCombobox {...inputProps} value={field.value} onChange={field.onChange} useInForm disabled={disabled} readOnly={readOnly} />
{formDescription && <FormDescription>{formDescription}</FormDescription>}
<FormMessage />
</FormItem>
)}
/>
}

View File

@@ -0,0 +1,174 @@
import * as React from "react"
import { Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui"
import {
Controller,
FormProvider,
useFormContext,
useFormState,
type ControllerProps,
type FieldPath,
type FieldValues,
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState } = useFormContext()
const formState = useFormState({ name: fieldContext.name })
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div
data-slot="form-item"
className={cn("grid gap-1.5", className)}
{...props}
/>
</FormItemContext.Provider>
)
}
function FormLabel({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const { error, formItemId } = useFormField()
return (
<Label
data-slot="form-label"
data-error={!!error}
className={className}
htmlFor={formItemId}
{...props}
/>
)
}
function FormRequiredIndicator({ className, ...props }: React.ComponentProps<"span">) {
return (
<span className={cn("text-ink-red-2", className)} {...props}>
*
</span>
)
}
function FormControl({ ...props }: React.ComponentProps<typeof SlotPrimitive.Slot>) {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<SlotPrimitive.Slot
data-slot="form-control"
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
}
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
const { formDescriptionId } = useFormField()
return (
<p
data-slot="form-description"
id={formDescriptionId}
className={cn("text-ink-gray-5 text-p-base", className)}
{...props}
/>
)
}
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message ?? "") : props.children
if (!body) {
return null
}
return (
<p
data-slot="form-message"
id={formMessageId}
className={cn("text-ink-red-4 text-p-base", className)}
{...props}
>
{body}
</p>
)
}
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
FormRequiredIndicator,
}

View File

@@ -0,0 +1,42 @@
import * as React from "react"
import { HoverCard as HoverCardPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function HoverCard({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
}
function HoverCardTrigger({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
return (
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
)
}
function HoverCardContent({
className,
align = "center",
sideOffset = 4,
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
return (
<HoverCardPrimitive.Portal data-slot="hover-card-portal">
<HoverCardPrimitive.Content
data-slot="hover-card-content"
align={align}
sideOffset={sideOffset}
className={cn(
"rounded-lg border bg-surface-modal shadow-xl text-ink-gray-8 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) p-4 outline-hidden",
className
)}
{...props}
/>
</HoverCardPrimitive.Portal>
)
}
export { HoverCard, HoverCardTrigger, HoverCardContent }

View File

@@ -0,0 +1,161 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
const inputGroupVariants = cva(cn("group/input-group relative flex w-full items-center outline-none min-w-0 border border-transparent transition-all",
// Variants based on alignment.
"has-[>[data-align=inline-start]]:[&>input]:ps-2",
"has-[>[data-align=inline-end]]:[&>input]:pe-2",
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
// Focus state.
"has-[[data-slot=input]:focus-visible]:bg-surface-white has-[[data-slot=input]:focus-visible]:border-outline-gray-4 has-[[data-slot=input]:focus-visible]:shadow-focus-gray",
// Disabled state
"has-[>[data-slot=input]:disabled]:bg-surface-gray-1 has-[>[data-slot=input]:disabled]:text-ink-gray-3 has-[>[data-slot=input]:disabled]:cursor-not-allowed has-[>[data-slot=input]:disabled]:pointer-events-none",
// Error state.
"has-[[data-slot][aria-invalid=true]]:shadow-focus-red has-[[data-slot][aria-invalid=true]]:border-outline-red-3",
// Read only state
"has-[[data-slot][aria-readonly=true]]:bg-surface-gray-1 has-[[data-slot][aria-readonly=true]]:text-ink-gray-6 has-[[data-slot][aria-readonly=true]]:pointer-events-none",
),
{
variants: {
variant: {
subtle: "bg-surface-gray-2",
outline: "bg-surface-white border-outline-gray-2"
},
size: {
sm: "h-7 has-[>textarea]:h-auto rounded text-base",
md: "h-8 has-[>textarea]:h-auto rounded text-base",
lg: "h-10 has-[>textarea]:h-auto rounded-md text-lg"
}
},
defaultVariants: {
variant: "subtle",
size: "md"
}
}
)
function InputGroup({ className, variant = "subtle", size = "md", ...props }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupVariants>) {
return (
<div
data-slot="input-group"
data-variant={variant}
data-size={size}
role="group"
className={cn(
inputGroupVariants({ variant, size }),
className
)}
{...props}
/>
)
}
const inputGroupAddonVariants = cva(
"text-ink-gray-5 flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
{
variants: {
align: {
"inline-start":
"order-first ps-3 has-[>button]:ms-[-0.45rem] has-[>kbd]:ms-[-0.35rem]",
"inline-end":
"order-last pe-3 has-[>button]:me-[-0.45rem] has-[>kbd]:me-[-0.35rem]",
"block-start":
"order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
"block-end":
"order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
},
},
defaultVariants: {
align: "inline-start",
},
}
)
function InputGroupAddon({
className,
align = "inline-start",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
return (
<div
role="group"
data-slot="input-group-addon"
data-align={align}
className={cn(inputGroupAddonVariants({ align }), className)}
onClick={(e) => {
if ((e.target as HTMLElement).closest("button")) {
return
}
e.currentTarget.parentElement?.querySelector("input")?.focus()
}}
{...props}
/>
)
}
const inputGroupButtonVariants = cva(
"text-sm shadow-none flex gap-2 items-center",
{
variants: {
size: {
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
"icon-xs":
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
},
},
defaultVariants: {
size: "xs",
},
}
)
function InputGroupButton({
className,
type = "button",
variant = "ghost",
size = "xs",
...props
}: Omit<React.ComponentProps<typeof Button>, "size"> &
VariantProps<typeof inputGroupButtonVariants>) {
return (
<Button
type={type}
data-size={size}
variant={variant}
className={cn(inputGroupButtonVariants({ size }), className)}
{...props}
/>
)
}
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"text-ink-gray-5 flex items-center gap-2 text-sm whitespace-nowrap [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
export {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
}

View File

@@ -0,0 +1,49 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { cva, VariantProps } from "class-variance-authority"
const inputVariants = cva(cn("flex w-full min-w-0 transition-all outline-none border border-transparent",
"focus-visible:bg-surface-white focus-visible:border-outline-gray-4 focus-visible:shadow-focus-gray",
"active:bg-surface-white active:shadow-sm active:border-outline-gray-4",
"placeholder:text-ink-gray-4 text-ink-gray-7",
"disabled:bg-surface-gray-1 disabled:placeholder:text-ink-gray-3 disabled:text-ink-gray-3 disabled:cursor-not-allowed disabled:pointer-events-none",
"aria-readonly:bg-surface-gray-1 aria-readonly:text-ink-gray-6 aria-readonly:pointer-events-none aria-invalid:shadow-focus-red aria-invalid:border-outline-red-3",
"in-data-[slot=input-group]:border-transparent! in-data-[slot=input-group]:focus-visible:shadow-none! in-data-[slot=input-group]:bg-transparent!"),
{
variants: {
inputSize: {
sm: "text-base rounded py-1.5 px-2 h-7",
md: "text-base rounded py-2 px-2.5 h-8",
lg: "text-lg rounded-md py-[11px] px-3 h-10",
},
variant: {
subtle: "bg-surface-gray-2 hover:bg-surface-gray-3 aria-invalid:bg-surface-red-1",
outline: "bg-surface-white border-outline-gray-2 hover:border-outline-gray-3 active:border-outline-gray-4 disabled:border-outline-gray-2",
}
},
defaultVariants: {
inputSize: "md",
variant: "subtle"
}
}
)
function Input({ className, type, inputSize = "md", variant = "subtle", ...props }: React.ComponentProps<"input"> & VariantProps<typeof inputVariants>) {
return (
<input
type={type}
data-slot="input"
data-input-size={inputSize}
data-variant={variant}
className={cn(
"file:text-ink-gray-8 file:inline-flex file:border-0 file:bg-transparent file:text-sm file:font-medium",
inputVariants({ inputSize, variant }),
className
)}
{...props}
/>
)
}
export { Input }

View File

@@ -0,0 +1,28 @@
import { cn } from "@/lib/utils"
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
return (
<kbd
data-slot="kbd"
className={cn(
"bg-surface-gray-2 py-0.5 text-ink-gray-5 pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none",
"[&_svg:not([class*='size-'])]:size-3",
"[[data-slot=tooltip-content]_&]:bg-surface-gray-6 [[data-slot=tooltip-content]_&]:text-ink-gray-1",
className
)}
{...props}
/>
)
}
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<kbd
data-slot="kbd-group"
className={cn("inline-flex items-center gap-1", className)}
{...props}
/>
)
}
export { Kbd, KbdGroup }

View File

@@ -0,0 +1,8 @@
export const KeyboardMetaKeyIcon = () => {
if (navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
return <span className="text-sm"></span>
} else {
return <span>Ctrl</span>
}
}

View File

@@ -0,0 +1,22 @@
import * as React from "react"
import { Label as LabelPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-base text-ink-gray-5 select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
className
)}
{...props}
/>
)
}
export { Label }

View File

@@ -0,0 +1,510 @@
import * as React from "react"
import {
type Cell,
type ColumnDef,
type ColumnSizingState,
type Header,
type OnChangeFn,
type Row,
type RowSelectionState,
flexRender,
functionalUpdate,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table"
import { useVirtualizer } from "@tanstack/react-virtual"
import { useDebounceCallback } from "usehooks-ts"
import { Checkbox } from "@/components/ui/checkbox"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
import { useDirection } from "./direction"
/** Optional per-column layout hints for `ListView`. */
export type ListViewColumnMeta = {
/** CSS grid track (`1fr`, `2fr`, `minmax(0,1fr)`). When set, used instead of TanStack pixel `size` in `grid-template-columns`. */
gridWidth?: string
align?: "left" | "center" | "right"
/**
* Tabular figures for stable digit width. Default: on when `align` is `right` (amounts); set `false` to opt out, or `true` for dates/IDs.
*/
tabularNums?: boolean
/**
* Full text for an overflow tooltip (shown only when the cell truncates). If omitted, a string `accessorKey` value is used when available.
*/
getTooltipText?: (row: unknown) => string | null | undefined
/** `false` disables the overflow tooltip for this column. */
truncateTooltip?: boolean
/**
* `false` skips single-line truncation for cells with custom layouts (e.g. action buttons). Default `true`.
*/
truncate?: boolean
}
function alignClass(meta: ListViewColumnMeta | undefined) {
switch (meta?.align) {
case "center":
return "justify-center text-center"
case "right":
return "justify-end text-end"
default:
return "justify-start text-start"
}
}
function tabularNumsClass(meta: ListViewColumnMeta | undefined) {
if (meta?.tabularNums === false) return ""
if (meta?.tabularNums === true) return "tabular-nums"
if (meta?.align === "right") return "tabular-nums"
return ""
}
function resolveTooltipLabel<TData>(
row: Row<TData>,
meta: ListViewColumnMeta | undefined,
columnDef: ColumnDef<TData, unknown>,
): string | undefined {
if (meta?.truncateTooltip === false) return undefined
const fromMeta = meta?.getTooltipText?.(row.original as unknown)
if (fromMeta != null && String(fromMeta).length > 0) {
return String(fromMeta)
}
const key = "accessorKey" in columnDef ? columnDef.accessorKey : undefined
if (key !== undefined && key !== null && key !== "") {
try {
const v = row.getValue(String(key))
if (v != null && v !== "") return String(v)
} catch {
/* column may not expose a value */
}
}
return undefined
}
function ListViewCellBody<TData>({
cell,
row,
meta,
children,
}: {
cell: Cell<TData, unknown>
row: Row<TData>
meta: ListViewColumnMeta | undefined
children: React.ReactNode
}) {
const ref = React.useRef<HTMLDivElement>(null)
const [overflowing, setOverflowing] = React.useState(false)
const direction = useDirection()
const tooltipLabel = resolveTooltipLabel(row, meta, cell.column.columnDef)
const tooltipAlign = meta?.align === "right" && direction === "ltr" ? "end" : "start"
const measure = React.useCallback(() => {
const el = ref.current
if (!el) return
setOverflowing(el.scrollWidth > el.clientWidth + 1)
}, [])
React.useLayoutEffect(() => {
measure()
}, [measure, children, tooltipLabel])
React.useEffect(() => {
const el = ref.current
if (!el || typeof ResizeObserver === "undefined") return
const ro = new ResizeObserver(measure)
ro.observe(el)
return () => ro.disconnect()
}, [measure])
if (meta?.truncate === false) {
return <div className="min-w-0 flex-1 overflow-visible">{children}</div>
}
const inner = (
<div
ref={ref}
className={cn(
"min-h-0 min-w-0 flex-1 truncate",
)}
>
{children}
</div>
)
if (!tooltipLabel || !overflowing) {
return inner
}
return (
<Tooltip delayDuration={400}>
<TooltipTrigger asChild>{inner}</TooltipTrigger>
<TooltipContent
side="bottom"
align={tooltipAlign}
className="max-w-sm text-balance wrap-break-word"
>
{tooltipLabel}
</TooltipContent>
</Tooltip>
)
}
function gridTemplateFromHeaders<TData>(headers: Header<TData, unknown>[]) {
return headers
.map((header) => {
const meta = header.column.columnDef.meta as ListViewColumnMeta | undefined
if (meta?.gridWidth) {
return meta.gridWidth
}
return `${header.getSize()}px`
})
.join(" ")
}
function defaultGetRowId<TData>(row: TData, index: number) {
const r = row as Record<string, unknown>
if (r && typeof r.name === "string") return r.name
if (r && typeof r.id === "string") return r.id
return String(index)
}
export type ListViewProps<TData> = {
data: TData[]
columns: ColumnDef<TData, unknown>[]
/**
* Stable row id for selection and keys. Defaults to `name`, then `id`, then row index (index is fragile if data order changes).
*/
getRowId?: (originalRow: TData, index: number) => string
/** Pixel height of each body row (default 40, matches frappe-ui ListView). */
rowHeight?: number
className?: string
/** Classes for the scrollable viewport (default includes max-height). */
scrollAreaClassName?: string
/** Max height of the scroll area; number is pixels. Default `420`. */
maxHeight?: number | string
emptyState?: React.ReactNode
enableColumnResizing?: boolean
columnSizing?: ColumnSizingState
onColumnSizingChange?: OnChangeFn<ColumnSizingState>
/** Debounced callback for persisting widths (e.g. localStorage). */
onColumnSizingCommit?: (sizing: ColumnSizingState) => void
columnSizingCommitDelayMs?: number
enableRowSelection?: boolean
rowSelection?: RowSelectionState
onRowSelectionChange?: OnChangeFn<RowSelectionState>
onRowClick?: (row: TData, event: React.MouseEvent) => void
}
function ListViewInner<TData>({
data,
columns: userColumns,
getRowId: getRowIdProp,
rowHeight = 40,
className,
scrollAreaClassName,
maxHeight = 420,
emptyState,
enableColumnResizing = true,
columnSizing: controlledColumnSizing,
onColumnSizingChange: controlledOnColumnSizingChange,
onColumnSizingCommit,
columnSizingCommitDelayMs = 250,
enableRowSelection = false,
rowSelection: controlledRowSelection,
onRowSelectionChange: controlledOnRowSelectionChange,
onRowClick,
}: ListViewProps<TData>) {
const parentRef = React.useRef<HTMLDivElement>(null)
const [internalColumnSizing, setInternalColumnSizing] = React.useState<ColumnSizingState>({})
const columnSizing = controlledColumnSizing ?? internalColumnSizing
const [internalRowSelection, setInternalRowSelection] = React.useState<RowSelectionState>({})
const rowSelection = controlledRowSelection ?? internalRowSelection
const setRowSelection = controlledOnRowSelectionChange ?? setInternalRowSelection
const debouncedSizingCommit = useDebounceCallback(
(sizing: ColumnSizingState) => {
onColumnSizingCommit?.(sizing)
},
columnSizingCommitDelayMs,
)
const selectionColumn = React.useMemo<ColumnDef<TData, unknown>>(
() => ({
id: "__list_view_select__",
size: 36,
minSize: 36,
maxSize: 36,
enableResizing: false,
meta: {
truncate: false,
truncateTooltip: false,
} satisfies ListViewColumnMeta,
header: ({ table }) => (
<div className="flex size-full items-center justify-center">
<Checkbox
aria-label="Select all rows"
checked={
table.getIsAllRowsSelected()
? true
: table.getIsSomeRowsSelected()
? "indeterminate"
: false
}
onCheckedChange={(value) => table.toggleAllRowsSelected(value === true)}
onClick={(e) => e.stopPropagation()}
/>
</div>
),
cell: ({ row }) => (
<div className="flex size-full items-center justify-center">
<Checkbox
aria-label="Select row"
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(value === true)}
onClick={(e) => e.stopPropagation()}
/>
</div>
),
}),
[],
)
const columns = React.useMemo(() => {
if (!enableRowSelection) return userColumns
return [selectionColumn, ...userColumns]
}, [enableRowSelection, selectionColumn, userColumns])
const getRowId = React.useCallback(
(originalRow: TData, index: number) =>
(getRowIdProp ?? defaultGetRowId)(originalRow, index),
[getRowIdProp],
)
const onColumnSizingChangeInternal = React.useCallback<OnChangeFn<ColumnSizingState>>(
(updater) => {
if (controlledOnColumnSizingChange) {
controlledOnColumnSizingChange(updater)
return
}
setInternalColumnSizing((old) => {
const next = functionalUpdate(updater, old)
debouncedSizingCommit(next)
return next
})
},
[controlledOnColumnSizingChange, debouncedSizingCommit],
)
const direction = useDirection()
const table = useReactTable({
data,
columns,
defaultColumn: {
minSize: 50,
size: 150,
},
columnResizeMode: "onChange",
columnResizeDirection: direction,
enableColumnResizing,
getCoreRowModel: getCoreRowModel(),
getRowId,
onColumnSizingChange: onColumnSizingChangeInternal,
onRowSelectionChange: setRowSelection,
state: {
columnSizing,
rowSelection,
},
enableRowSelection,
})
const headerGroup = table.getHeaderGroups()[0]
const gridTemplateColumns = headerGroup
? gridTemplateFromHeaders(headerGroup.headers)
: ""
const { rows } = table.getRowModel()
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => rowHeight,
overscan: 10,
})
const maxHeightStyle =
typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight
if (data.length === 0) {
return (
<div
className={cn(
"bg-surface-gray-2 text-ink-gray-5 flex min-h-32 items-center justify-center rounded-md px-4 text-sm",
className,
)}
>
{emptyState ?? "No data"}
</div>
)
}
/** Tracks + column gaps + horizontal padding (`px-2` × 2) so header and body share one scroll width. */
const colCount = headerGroup?.headers.length ?? 0
const minTableOuterWidth =
table.getCenterTotalSize() +
Math.max(0, colCount - 1) * 16 +
16
return (
<div className={cn("flex min-w-0 flex-col", className)} role="grid">
<div
ref={parentRef}
className={cn("min-h-0 overflow-auto", scrollAreaClassName)}
style={{ maxHeight: maxHeightStyle }}
>
{headerGroup ? (
<div
className="bg-surface-gray-2 sticky top-0 z-10 mb-2 grid w-full items-center gap-x-4 rounded p-2"
role="row"
style={{
display: "grid",
gridTemplateColumns,
minWidth: `max(100%, ${minTableOuterWidth}px)`,
boxSizing: "border-box",
}}
>
{headerGroup.headers.map((header) => {
const meta = header.column.columnDef.meta as ListViewColumnMeta | undefined
return (
<div
key={header.id}
className={cn(
"text-ink-gray-5 group relative flex min-w-0 items-center px-0 text-sm",
alignClass(meta),
)}
role="columnheader"
>
<div className="min-w-0 flex-1 truncate">
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</div>
{enableColumnResizing && header.column.getCanResize() ? (
<>
<span
aria-hidden
className={cn(
"pointer-events-none absolute ltr:-right-2 rtl:-left-2 z-1 w-0.5 bg-gray-400",
"opacity-0 transition-[opacity,background-color] ease-in-out duration-150",
"group-hover:opacity-100 group-hover:bg-gray-400",
header.column.getIsResizing() && "bg-outline-gray-6 opacity-100",
)}
style={{ height: "100%" }}
/>
<div
role="separator"
aria-orientation="vertical"
aria-label="Resize column"
onMouseDown={(e) => {
e.preventDefault()
document.body.classList.add("select-none", "cursor-col-resize")
const end = () => {
document.body.classList.remove("select-none", "cursor-col-resize")
window.removeEventListener("mouseup", end)
window.removeEventListener("touchend", end)
}
window.addEventListener("mouseup", end)
window.addEventListener("touchend", end)
header.getResizeHandler()(e)
}}
onTouchStart={header.getResizeHandler()}
className="absolute top-0 ltr:-right-2 rtl:-left-2 z-10 h-full w-2 max-w-[12px] cursor-col-resize touch-none select-none bg-transparent"
/>
</>
) : null}
</div>
)
})}
</div>
) : null}
<div
className="relative w-full"
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
minWidth: `max(100%, ${minTableOuterWidth}px)`,
boxSizing: "border-box",
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const row = rows[virtualRow.index]
if (!row) return null
const leadDataColumnIndex = enableRowSelection ? 1 : 0
return (
<div
key={row.id}
data-index={virtualRow.index}
role="row"
className={cn(
"ease-in-out absolute top-0 ltr:left-0 rtl:right-0 w-full min-w-0 rounded px-2 transition-all duration-300",
// virtualRow.index > 0 && "border-t border-outline-gray-1",
!row.getIsSelected() && "hover:bg-surface-menu-bar",
row.getIsSelected() && "bg-surface-gray-2 hover:bg-surface-gray-3",
onRowClick && "cursor-pointer",
)}
style={{
display: "grid",
gridTemplateColumns,
boxSizing: "border-box",
columnGap: "1rem",
height: `${rowHeight}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
onClick={(e) => {
if (onRowClick) onRowClick(row.original, e)
}}
>
{virtualRow.index > 0 && <div className="absolute top-0 inset-s-2 inset-e-2 h-px bg-outline-gray-1" />}
{row.getVisibleCells().map((cell, cellIndex) => {
const meta = cell.column.columnDef.meta as ListViewColumnMeta | undefined
return (
<div
key={cell.id}
role="gridcell"
className={cn(
"flex min-w-0 items-center overflow-hidden text-sm",
cellIndex === leadDataColumnIndex
? "text-ink-gray-8"
: "text-ink-gray-7",
alignClass(meta),
tabularNumsClass(meta),
)}
>
<ListViewCellBody cell={cell} row={row} meta={meta}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</ListViewCellBody>
</div>
)
})}
</div>
)
})}
</div>
</div>
</div>
)
}
/**
* Div-based list with CSS Grid columns, optional resize handles, row virtualization, and frappe-uialigned Espresso tokens.
*/
export function ListView<TData>(props: ListViewProps<TData>) {
return <ListViewInner {...props} />
}
export type { ColumnSizingState, RowSelectionState }

View File

@@ -0,0 +1,27 @@
import { Skeleton } from "./skeleton"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table"
export const TableLoader = ({ rows = 10, columns = 5 }: { rows?: number, columns?: number }) => {
return <Table>
<TableHeader>
<TableRow>
{Array.from({ length: columns }).map((_, index) => (
<TableHead key={index}>
<Skeleton className="h-4 w-full" />
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{Array.from({ length: rows }).map((_, index) => (
<TableRow key={index}>
{Array.from({ length: columns }).map((_, index) => (
<TableCell key={index}>
<Skeleton className="h-4 w-full" />
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
}

View File

@@ -0,0 +1,28 @@
import React from 'react'
import rehypeRaw from 'rehype-raw'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
// import './markdown.css'
interface MarkdownRendererProps {
content: string,
className?: string
}
const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content }) => {
return <ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
// components={{
// p: (props) => <Text {...props} as='p' />,
// ul: (props) => <UnorderedList {...props} />,
// ol: (props) => <OrderedList {...props} />,
// li: (props) => <ListItem {...props} />,
// a: (props) => <Link {...props} />,
// }}>
>
{content}
</ReactMarkdown>
}
export default MarkdownRenderer

View File

@@ -0,0 +1,87 @@
import * as React from "react"
import { Popover as PopoverPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Popover({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />
}
function PopoverTrigger({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
}
function PopoverContent({
className,
align = "center",
sideOffset = 4,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
"bg-surface-modal rounded-lg border p-3 shadow-xl outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin)",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
)
}
function PopoverAnchor({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
}
function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="popover-header"
className={cn("flex flex-col gap-1 text-sm", className)}
{...props}
/>
)
}
function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) {
return (
<div
data-slot="popover-title"
className={cn("font-medium", className)}
{...props}
/>
)
}
function PopoverDescription({
className,
...props
}: React.ComponentProps<"p">) {
return (
<p
data-slot="popover-description"
className={cn("text-ink-gray-5", className)}
{...props}
/>
)
}
export {
Popover,
PopoverTrigger,
PopoverContent,
PopoverAnchor,
PopoverHeader,
PopoverTitle,
PopoverDescription,
}

View File

@@ -0,0 +1,67 @@
import * as React from "react"
import { Progress as ProgressPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { cva, VariantProps } from "class-variance-authority"
const progressVariants = cva(
"bg-surface-gray-2 relative w-full overflow-hidden rounded-full",
{
variants: {
size: {
sm: "h-0.5",
md: "h-1",
lg: "h-2.5",
xl: "h-3"
}
}
}
)
interface ProgressProps extends React.ComponentProps<typeof ProgressPrimitive.Root>, VariantProps<typeof progressVariants> {
/** Optional text label displayed on the progress bar */
label?: React.ReactNode,
/** Whether to show a hint/tooltip for the progress value */
hint?: boolean,
/** Override the default hint text with custom progress value */
hintText?: React.ReactNode
}
function Progress({
className,
value,
size = "sm",
label,
hint,
hintText,
...props
}: ProgressProps) {
const progressValue = hintText ? hintText : `${value}%`
return (
<div className="flex flex-col gap-2.5">
{label || hint ? <div className="flex items-center justify-between gap-1">
{label && <span className="text-base font-medium text-ink-gray-7">{label}</span>}
{hint && <span className="text-base font-medium text-ink-gray-5">{progressValue}</span>}
</div> : null}
<ProgressPrimitive.Root
data-slot="progress"
data-size={size}
className={cn(
progressVariants({ size }),
className
)}
{...props}
>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="bg-surface-gray-7 rounded-xl h-full w-full flex-1 transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
</div>
)
}
export { Progress }

View File

@@ -0,0 +1,43 @@
import * as React from "react"
import { CircleIcon } from "lucide-react"
import { RadioGroup as RadioGroupPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function RadioGroup({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn("grid gap-3", className)}
{...props}
/>
)
}
function RadioGroupItem({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={cn(
"border-outline-gray-2 text-ink-gray-7 focus-visible:border-outline-gray-1 focus-visible:ring-outline-gray-1/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator
data-slot="radio-group-indicator"
className="relative flex items-center justify-center"
>
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
}
export { RadioGroup, RadioGroupItem }

View File

@@ -0,0 +1,221 @@
import * as React from "react"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { Select as SelectPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { cva, VariantProps } from "class-variance-authority"
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
const selectVariants = cva(cn("flex w-fit items-center justify-between gap-2 min-w-0 transition-all outline-none border border-transparent whitespace-nowrap",
"focus-visible:bg-surface-white focus-visible:border-outline-gray-4 focus-visible:shadow-focus-gray",
"active:bg-surface-white active:shadow-sm active:border-outline-gray-4 data-[state=open]:border-outline-gray-4",
"placeholder:text-ink-gray-4 text-ink-gray-7",
"disabled:bg-surface-gray-1 disabled:placeholder:text-ink-gray-3 disabled:text-ink-gray-3 disabled:cursor-not-allowed disabled:pointer-events-none",
"aria-readonly:bg-surface-gray-1 aria-readonly:text-ink-gray-6 aria-readonly:pointer-events-none aria-invalid:shadow-focus-red aria-invalid:border-outline-red-3",
// Disable most styles inside an input group
"in-data-[slot=input-group]:border-transparent! in-data-[slot=input-group]:focus-visible:shadow-none! in-data-[slot=input-group]:bg-transparent!"),
{
variants: {
inputSize: {
sm: "text-base rounded py-1.5 px-2 h-7",
md: "text-base rounded py-2 px-2.5 h-8",
lg: "text-lg rounded-md py-[11px] px-3 h-10",
},
variant: {
subtle: "bg-surface-gray-2 hover:bg-surface-gray-3 aria-invalid:bg-surface-red-1",
outline: "bg-surface-white border-outline-gray-2 hover:border-outline-gray-3 active:border-outline-gray-4 disabled:border-outline-gray-2",
}
},
defaultVariants: {
inputSize: "md",
variant: "subtle"
}
}
)
function SelectTrigger({
className,
inputSize = "md",
variant = "subtle",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & VariantProps<typeof selectVariants>) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-input-size={inputSize}
className={cn(
"data-placeholder:text-ink-gray-4 [&_svg:not([class*='text-'])]:text-ink-gray-7",
"*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
selectVariants({ inputSize, variant }),
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "popper",
align = "center",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-surface-modal rounded-lg min-w-32 border shadow-xl",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
align={align}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-ink-gray-4 px-2 py-1.5 text-sm font-medium", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"outline-hidden select-none relative flex w-full cursor-default items-center gap-2 rounded py-1.5 pe-8 px-2",
"focus:bg-surface-gray-2 text-ink-gray-6 [&_svg:not([class*='text-'])]:text-ink-gray-6 text-base [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
"data-disabled:pointer-events-none data-disabled:text-ink-gray-3",
className
)}
{...props}
>
<span
data-slot="select-item-indicator"
className="absolute ltr:right-2 rtl:left-2 flex size-3.5 items-center justify-center"
>
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-outline-gray-modals pointer-events-none mx-0.5 my-1 h-px", className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}

Some files were not shown because too many files have changed in this diff Show More