Compare commits

...

79 Commits

Author SHA1 Message Date
Frappe PR Bot
fdfcbf72bd chore(release): Bumped to Version 16.19.0
# [16.19.0](https://github.com/frappe/erpnext/compare/v16.18.3...v16.19.0) (2026-05-20)

### Bug Fixes

* add filter subtitle in print formats ([c4037da](c4037daca8))
* add warehouse vaildation for repack entry (backport [#54866](https://github.com/frappe/erpnext/issues/54866)) ([#54901](https://github.com/frappe/erpnext/issues/54901)) ([596c257](596c2571f6))
* changes to gl print template ([caa524f](caa524f661))
* disallow editing on reversal journals ([6a53982](6a53982f4a))
* **general-ledger:** show raw GL entries when categorize_by is empty (backport [#54816](https://github.com/frappe/erpnext/issues/54816)) ([#54830](https://github.com/frappe/erpnext/issues/54830)) ([c041cd2](c041cd27b5))
* handle None delivery_date when sorting MPS data (backport [#55028](https://github.com/frappe/erpnext/issues/55028)) ([#55059](https://github.com/frappe/erpnext/issues/55059)) ([f272d32](f272d32f80))
* improve design and refactor ar print template ([059372a](059372add5))
* improve filter details render logic to avoid showing duplicate information ([040b31d](040b31d3a7))
* incoming rate for legacy serial no (backport [#54962](https://github.com/frappe/erpnext/issues/54962)) ([#54978](https://github.com/frappe/erpnext/issues/54978)) ([6bce78c](6bce78c66d))
* minor bug fixes for ar print template ([09b19f7](09b19f7a2a))
* minor bugs in print templates ([e1446fc](e1446fc6f4))
* minor changes in print template ([0ead229](0ead2296e6))
* minor changes in print template ([16bc28b](16bc28bd70))
* minor changes in print templates ([0d50e03](0d50e03595))
* minor text issues in print ([daaa4ca](daaa4ca0c8))
* normalize date comparison to avoid datatype mismatch ([42f6cb4](42f6cb40d1))
* **patch:** drop dead procedures first before other changes ([0df9591](0df9591910))
* **payment_entry:** fix paid/received amount calculation for multi-currency accounts (backport [#54963](https://github.com/frappe/erpnext/issues/54963)) ([#54970](https://github.com/frappe/erpnext/issues/54970)) ([48b09eb](48b09eb52e))
* posting date and time ([1c44c60](1c44c60dbd))
* prevent duplicate task execution and timestamp error in transaction deletion (backport [#55021](https://github.com/frappe/erpnext/issues/55021)) ([#55025](https://github.com/frappe/erpnext/issues/55025)) ([9857cc6](9857cc64d6))
* remove parent page ([10b4090](10b409005d))
* remove sql procedure method from AR report ([414319d](414319daeb))
* revamp print formats for accounts receivable summary and accounts payable summary reports ([928fab6](928fab6f7e))
* status not changing for dropshipped POs and SOs (backport [#54934](https://github.com/frappe/erpnext/issues/54934)) ([#54937](https://github.com/frappe/erpnext/issues/54937)) ([3c571a1](3c571a1691))
* stock balance showing incorrect value because of incorrect SLE ([0b3344b](0b3344bad9))
* **stock:** add whole number quantity validation in Stock Reconciliation (backport [#54922](https://github.com/frappe/erpnext/issues/54922)) ([#54925](https://github.com/frappe/erpnext/issues/54925)) ([c499454](c4994548c3))
* **stock:** ignore fetching warehouse account for asset items (backport [#54403](https://github.com/frappe/erpnext/issues/54403)) ([#54961](https://github.com/frappe/erpnext/issues/54961)) ([5e5b5cf](5e5b5cfa0c))
* **stock:** update buying amount calculation in gross profit report (backport [#55020](https://github.com/frappe/erpnext/issues/55020)) ([#55024](https://github.com/frappe/erpnext/issues/55024)) ([8870619](88706192d7))
* styling in trial_balance.html and print format ([9a18d31](9a18d318d9))
* toast message for item price insert ([#55009](https://github.com/frappe/erpnext/issues/55009)) ([c967792](c967792ccb))
* use route_options for Credit Note and Debit Note sidebar links (backport [#55026](https://github.com/frappe/erpnext/issues/55026)) ([#55063](https://github.com/frappe/erpnext/issues/55063)) ([1941c3b](1941c3b136))
* **UX:** Buying settings form cleanup ([#54731](https://github.com/frappe/erpnext/issues/54731)) ([e7ae296](e7ae296614))
* **UX:** Item master form cleanup ([#54538](https://github.com/frappe/erpnext/issues/54538)) ([0eb049c](0eb049cd85))
* validate company region in uae vat 201 (backport [#54899](https://github.com/frappe/erpnext/issues/54899)) ([#55055](https://github.com/frappe/erpnext/issues/55055)) ([4015c2b](4015c2b9a4))
* warn when accounting dimension fieldname conflicts with existing fields (backport [#55036](https://github.com/frappe/erpnext/issues/55036)) ([#55062](https://github.com/frappe/erpnext/issues/55062)) ([68a5eae](68a5eae3ff))

### Features

* add print format for accounts payable report ([1c6dc80](1c6dc80b70))
* introduce print format for Accounts Receivable report ([4e7f2ee](4e7f2eeaa0))
* introduce print formats for financial statements ([3283c46](3283c461f1))
* print format for report trial balance ([1d08448](1d08448d1a))

### Reverts

* Revert "fix: debit credit not equal in purchase transactions for mult… (backport [#54906](https://github.com/frappe/erpnext/issues/54906)) ([#54908](https://github.com/frappe/erpnext/issues/54908)) ([0d07083](0d07083299))
2026-05-20 04:10:54 +00:00
diptanilsaha
fb7f820885 Merge pull request #55051 from frappe/version-16-hotfix
chore: release v16
2026-05-20 09:39:15 +05:30
mergify[bot]
1941c3b136 fix: use route_options for Credit Note and Debit Note sidebar links (backport #55026) (#55063)
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
fix: use route_options for Credit Note and Debit Note sidebar links (#55026)
2026-05-20 00:37:32 +05:30
mergify[bot]
f272d32f80 fix: handle None delivery_date when sorting MPS data (backport #55028) (#55059)
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
fix: handle None delivery_date when sorting MPS data (#55028)
2026-05-20 00:37:14 +05:30
mergify[bot]
68a5eae3ff fix: warn when accounting dimension fieldname conflicts with existing fields (backport #55036) (#55062)
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
fix: warn when accounting dimension fieldname conflicts with existing fields (#55036)
2026-05-19 23:34:44 +05:30
ruthra kumar
1b07844237 Merge pull request #55057 from frappe/mergify/bp/version-16-hotfix/pr-55053
fix(patch): drop dead procedures first before other changes (backport #55053)
2026-05-19 17:05:41 +05:30
mergify[bot]
4015c2b9a4 fix: validate company region in uae vat 201 (backport #54899) (#55055)
Co-authored-by: Ravibharathi <131471282+ravibharathi656@users.noreply.github.com>
fix: validate company region in uae vat 201 (#54899)
2026-05-19 11:29:17 +00:00
ruthra kumar
0df9591910 fix(patch): drop dead procedures first before other changes
(cherry picked from commit 61d24ba55f)
2026-05-19 11:08:51 +00:00
rohitwaghchaure
4403e1c0f4 Merge pull request #55048 from frappe/mergify/bp/version-16-hotfix/pr-55046
fix: stock balance showing incorrect value because of incorrect SLE (backport #55046)
2026-05-19 14:14:34 +05:30
Rohit Waghchaure
0b3344bad9 fix: stock balance showing incorrect value because of incorrect SLE
(cherry picked from commit 94b95d6c2f)
2026-05-19 08:22:34 +00:00
Ravibharathi
5cc335dd53 Merge pull request #55042 from frappe/mergify/bp/version-16-hotfix/pr-54761
fix: normalize date comparison to avoid datatype mismatch (backport #54761)
2026-05-19 11:47:25 +05:30
ervishnucs
42f6cb40d1 fix: normalize date comparison to avoid datatype mismatch
(cherry picked from commit 01e382b106)
2026-05-19 05:57:54 +00:00
mergify[bot]
88706192d7 fix(stock): update buying amount calculation in gross profit report (backport #55020) (#55024)
Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com>
fix(stock): update buying amount calculation in gross profit report (#55020)
2026-05-19 09:44:48 +05:30
mergify[bot]
9857cc64d6 fix: prevent duplicate task execution and timestamp error in transaction deletion (backport #55021) (#55025)
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
fix: prevent duplicate task execution and timestamp error in transaction deletion (#55021)
2026-05-18 17:55:05 +00:00
ruthra kumar
ff0533d085 Merge pull request #54818 from frappe/mergify/bp/version-16-hotfix/pr-54783
fix: disallow editing on reversal journals (backport #54783)
2026-05-18 15:41:05 +05:30
ruthra kumar
b0f770780c Merge pull request #55015 from frappe/mergify/bp/version-16-hotfix/pr-55001
fix: remove sql procedure method from AR report (backport #55001)
2026-05-18 14:09:00 +05:30
ruthra kumar
414319daeb fix: remove sql procedure method from AR report
(cherry picked from commit 63a7142b9b)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.py
#	erpnext/patches.txt
2026-05-18 13:51:16 +05:30
Nishka Gosalia
1945a2fe39 Merge pull request #55011 from frappe/mergify/bp/version-16-hotfix/pr-55009
fix: toast message for item price insert (backport #55009)
2026-05-18 11:59:48 +05:30
Nishka Gosalia
c967792ccb fix: toast message for item price insert (#55009)
(cherry picked from commit ae9c632e39)
2026-05-18 06:11:36 +00:00
Soham Kulkarni
253248c8e8 Merge pull request #55002 from frappe/mergify/bp/version-16-hotfix/pr-55000
fix: remove parent page (backport #55000)
2026-05-18 10:58:22 +05:30
sokumon
10b409005d fix: remove parent page
(cherry picked from commit e13bd9eaa6)
2026-05-18 05:01:35 +00:00
Ejaaz Khan
272ea30031 Merge pull request #54975 from frappe/mergify/bp/version-16-hotfix/pr-54655
refactor: remove dead print format (backport #54655)
2026-05-18 10:07:20 +05:30
MochaMind
098579ffbc chore: update POT file (#54990) 2026-05-17 21:43:32 +02:00
mergify[bot]
6bce78c66d fix: incoming rate for legacy serial no (backport #54962) (#54978)
fix: incoming rate for legacy serial no

(cherry picked from commit 2773b7c002)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2026-05-16 20:48:28 +05:30
mergify[bot]
5e5b5cfa0c fix(stock): ignore fetching warehouse account for asset items (backport #54403) (#54961)
* fix(stock): ignore fetching warehouse account for asset items

(cherry picked from commit 6fe08428c1)

* test(stock): add test to create pr for asset item without checking the stock account

(cherry picked from commit 8cf4402823)

---------

Co-authored-by: Sudharsanan11 <sudharsananashok1975@gmail.com>
2026-05-16 11:52:23 +00:00
Ejaaz Khan
2c78b6c36a refactor: remove dead print format
(cherry picked from commit c933c2bd53)
2026-05-15 11:57:31 +00:00
mergify[bot]
48b09eb52e fix(payment_entry): fix paid/received amount calculation for multi-currency accounts (backport #54963) (#54970)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-05-15 10:33:33 +00:00
ruthra kumar
799d6d159c Merge pull request #54960 from frappe/mergify/bp/version-16/pr-54941
fix: flag to disable opening balance calculation in general ledger (backport #54941)
2026-05-15 14:31:57 +05:30
ruthra kumar
48f59a033f refactor: flag to disable opening balance calculation
(cherry picked from commit 28a2230d02)
2026-05-15 07:32:52 +00:00
ruthra kumar
1716026e11 Merge pull request #54957 from frappe/mergify/bp/version-16-hotfix/pr-54941
fix: flag to disable opening balance calculation in general ledger (backport #54941)
2026-05-15 12:41:25 +05:30
ruthra kumar
b1e356fd97 refactor: flag to disable opening balance calculation
(cherry picked from commit 28a2230d02)
2026-05-15 06:51:20 +00:00
Frappe PR Bot
2807c9f08f chore(release): Bumped to Version 16.18.3
## [16.18.3](https://github.com/frappe/erpnext/compare/v16.18.2...v16.18.3) (2026-05-14)

### Bug Fixes

* status not changing for dropshipped POs and SOs (backport [#54934](https://github.com/frappe/erpnext/issues/54934)) (backport [#54937](https://github.com/frappe/erpnext/issues/54937)) ([#54938](https://github.com/frappe/erpnext/issues/54938)) ([5271773](5271773595))
2026-05-14 09:39:02 +00:00
mergify[bot]
5271773595 fix: status not changing for dropshipped POs and SOs (backport #54934) (backport #54937) (#54938)
fix: status not changing for dropshipped POs and SOs (backport #54934) (#54937)

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

(cherry picked from commit 78a79120ea)


(cherry picked from commit 3c571a1691)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-05-14 09:37:26 +00:00
mergify[bot]
3c571a1691 fix: status not changing for dropshipped POs and SOs (backport #54934) (#54937)
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

(cherry picked from commit 78a79120ea)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-05-14 14:47:35 +05:30
Nishka Gosalia
9a9b330af6 Merge pull request #54910 from frappe/mergify/bp/version-16-hotfix/pr-54731
fix(UX): Buying settings form cleanup (backport #54731)
2026-05-14 11:56:28 +05:30
Frappe PR Bot
dd35cd1f84 chore(release): Bumped to Version 16.18.2
## [16.18.2](https://github.com/frappe/erpnext/compare/v16.18.1...v16.18.2) (2026-05-14)

### Bug Fixes

* posting date and time ([ab09029](ab090295d9))
2026-05-14 05:35:39 +00:00
rohitwaghchaure
77a6299e8b Merge pull request #54931 from frappe/mergify/bp/version-16/pr-54928
fix: posting date and time (backport #54905) (backport #54928)
2026-05-14 11:04:07 +05:30
rohitwaghchaure
b79ec7cbdd chore: fix linter issue
(cherry picked from commit 3c993377aa)
(cherry picked from commit 21ada7799c)
2026-05-14 02:04:52 +00:00
rohitwaghchaure
927360dd1d chore: fixed test case
(cherry picked from commit c740f77a6f)
(cherry picked from commit f4e66914c6)
2026-05-14 02:04:52 +00:00
Rohit Waghchaure
ab090295d9 fix: posting date and time
(cherry picked from commit fb6c05f186)
(cherry picked from commit 1c44c60dbd)
2026-05-14 02:04:51 +00:00
rohitwaghchaure
76204b920d Merge pull request #54928 from frappe/mergify/bp/version-16-hotfix/pr-54905
fix: posting date and time (backport #54905)
2026-05-14 07:33:26 +05:30
rohitwaghchaure
21ada7799c chore: fix linter issue
(cherry picked from commit 3c993377aa)
2026-05-13 18:19:17 +00:00
rohitwaghchaure
f4e66914c6 chore: fixed test case
(cherry picked from commit c740f77a6f)
2026-05-13 18:19:17 +00:00
Rohit Waghchaure
1c44c60dbd fix: posting date and time
(cherry picked from commit fb6c05f186)
2026-05-13 18:19:17 +00:00
mergify[bot]
596c2571f6 fix: add warehouse vaildation for repack entry (backport #54866) (#54901)
fix: add warehouse vaildation for repack entry (#54866)

(cherry picked from commit bc07b2d3e5)

Co-authored-by: Pandiyan P <pandiyanpalani37@gmail.com>
2026-05-13 15:24:51 +00:00
mergify[bot]
c4994548c3 fix(stock): add whole number quantity validation in Stock Reconciliation (backport #54922) (#54925)
fix(stock): add whole number quantity validation in Stock Reconciliation (#54922)

(cherry picked from commit f9dec73042)

Co-authored-by: Pandiyan P <pandiyanpalani37@gmail.com>
2026-05-13 15:03:45 +00:00
Khushi Rawat
604f80f043 Merge pull request #54913 from frappe/mergify/bp/version-16-hotfix/pr-53843
feat: Accounts Payable print template revamp and print format introduction (backport #53843)
2026-05-13 16:07:00 +05:30
Khushi Rawat
4a4757dbc6 Merge pull request #54915 from frappe/mergify/bp/version-16-hotfix/pr-53762
feat: General ledger print template revamp and print format introduction (backport #53762)
2026-05-13 16:06:27 +05:30
Khushi Rawat
436e0269f8 Merge pull request #54914 from frappe/mergify/bp/version-16-hotfix/pr-53822
feat: Accounts Receivable print template revamp and print format introduction (backport #53822)
2026-05-13 16:05:47 +05:30
Khushi Rawat
5d73da5a85 Merge pull request #54912 from frappe/mergify/bp/version-16-hotfix/pr-53870
feat: AR and AP summary reports print template revamp and print format introduction (backport #53870)
2026-05-13 15:57:07 +05:30
Khushi Rawat
886c7cc5a3 Merge pull request #54911 from frappe/mergify/bp/version-16-hotfix/pr-53934
feat: Financial Statements print format introduction (backport #53934)
2026-05-13 15:56:45 +05:30
Frappe PR Bot
c4b7b15824 chore(release): Bumped to Version 16.18.1
## [16.18.1](https://github.com/frappe/erpnext/compare/v16.18.0...v16.18.1) (2026-05-13)

### Reverts

* Revert "fix: debit credit not equal in purchase transactions for mult… (backport [#54906](https://github.com/frappe/erpnext/issues/54906)) (backport [#54908](https://github.com/frappe/erpnext/issues/54908)) ([#54916](https://github.com/frappe/erpnext/issues/54916)) ([cfd3847](cfd3847255))
2026-05-13 10:16:21 +00:00
mergify[bot]
cfd3847255 Revert "fix: debit credit not equal in purchase transactions for mult… (backport #54906) (backport #54908) (#54916)
Revert "fix: debit credit not equal in purchase transactions for mult… (backport #54906) (#54908)

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.

(cherry picked from commit cf5e8ce878)


(cherry picked from commit 0d07083299)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-05-13 10:14:46 +00:00
Khushi Rawat
d430736177 Merge pull request #54634 from frappe/mergify/bp/version-16-hotfix/pr-54538
fix(UX): Item master form cleanup (backport #54538)
2026-05-13 15:11:31 +05:30
Shllokkk
caa524f661 fix: changes to gl print template
(cherry picked from commit e8d08df044)
2026-05-13 09:41:22 +00:00
Shllokkk
cd69b66761 refactor: table body data rendering cleanup
(cherry picked from commit 0d4f56bf84)
2026-05-13 09:41:22 +00:00
Shllokkk
040b31d3a7 fix: improve filter details render logic to avoid showing duplicate information
(cherry picked from commit 9660debe28)
2026-05-13 09:41:21 +00:00
Shllokkk
04893ae0e3 refactor: clean and standardize print template for general ledger report
(cherry picked from commit 3ba36212b0)
2026-05-13 09:41:21 +00:00
Shllokkk
0ead2296e6 fix: minor changes in print template
(cherry picked from commit e3019c827c)
2026-05-13 09:40:53 +00:00
Shllokkk
09b19f7a2a fix: minor bug fixes for ar print template
(cherry picked from commit 4228885f1e)
2026-05-13 09:40:52 +00:00
Shllokkk
4e7f2eeaa0 feat: introduce print format for Accounts Receivable report
(cherry picked from commit e6a32a9d02)
2026-05-13 09:40:52 +00:00
Shllokkk
059372add5 fix: improve design and refactor ar print template
(cherry picked from commit ffc59ebc9c)
2026-05-13 09:40:52 +00:00
Shllokkk
16bc28bd70 fix: minor changes in print template
(cherry picked from commit 915fcc0166)
2026-05-13 09:40:24 +00:00
Shllokkk
1c6dc80b70 feat: add print format for accounts payable report
(cherry picked from commit 2bf9d41797)
2026-05-13 09:40:24 +00:00
Shllokkk
748a3d72a3 refactor: revamp print template for accounts payable report
(cherry picked from commit c051536182)
2026-05-13 09:40:23 +00:00
Shllokkk
0d50e03595 fix: minor changes in print templates
(cherry picked from commit 44e0b36093)
2026-05-13 09:39:56 +00:00
Shllokkk
e1446fc6f4 fix: minor bugs in print templates
(cherry picked from commit 86ee9959a2)
2026-05-13 09:39:56 +00:00
Shllokkk
928fab6f7e fix: revamp print formats for accounts receivable summary and accounts payable summary reports
(cherry picked from commit 5bbcb73808)
2026-05-13 09:39:55 +00:00
mergify[bot]
0d07083299 Revert "fix: debit credit not equal in purchase transactions for mult… (backport #54906) (#54908)
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.

(cherry picked from commit cf5e8ce878)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-05-13 15:09:38 +05:30
Shllokkk
c4037daca8 fix: add filter subtitle in print formats
(cherry picked from commit e82b4d9ca7)
2026-05-13 09:39:05 +00:00
Shllokkk
9a18d318d9 fix: styling in trial_balance.html and print format
(cherry picked from commit 5858b14071)
2026-05-13 09:39:05 +00:00
Shllokkk
2ff9f00ce0 refactor: print templates for financial statements
(cherry picked from commit e8777a1e34)
2026-05-13 09:39:05 +00:00
Shllokkk
daaa4ca0c8 fix: minor text issues in print
(cherry picked from commit fa0a9085ca)
2026-05-13 09:39:04 +00:00
Shllokkk
1d08448d1a feat: print format for report trial balance
(cherry picked from commit ac7e5271b0)
2026-05-13 09:39:04 +00:00
Shllokkk
3283c461f1 feat: introduce print formats for financial statements
(cherry picked from commit 82cac9c40f)
2026-05-13 09:39:04 +00:00
Nishka Gosalia
e7ae296614 fix(UX): Buying settings form cleanup (#54731)
* fix(UX): Buying settings form cleanup

* fix: controller approach modification

* fix: dark mode support

(cherry picked from commit 45f05fbeaa)
2026-05-13 09:24:12 +00:00
mergify[bot]
c041cd27b5 fix(general-ledger): show raw GL entries when categorize_by is empty (backport #54816) (#54830)
fix(general-ledger): show raw GL entries when categorize_by is empty (#54816)

(cherry picked from commit dfbe847307)

Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com>
2026-05-13 05:42:11 +05:30
ruthra kumar
6a53982f4a fix: disallow editing on reversal journals
(cherry picked from commit 26ca7445eb)
2026-05-11 04:38:45 +00:00
Khushi Rawat
0eb049cd85 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

(cherry picked from commit 43937acd8b)
2026-04-29 09:15:20 +00:00
85 changed files with 4099 additions and 1846 deletions

View File

@@ -6,7 +6,7 @@ import frappe
from frappe.model.document import Document
from frappe.utils.user import is_website_user
__version__ = "16.18.0"
__version__ = "16.19.0"
def get_default_company(user=None):

View File

@@ -43,6 +43,7 @@ class AccountingDimension(Document):
def validate(self):
self.validate_doctype()
validate_column_name(self.fieldname)
self.validate_fieldname_conflict()
self.validate_dimension_defaults()
def validate_doctype(self):
@@ -74,6 +75,27 @@ class AccountingDimension(Document):
message += _("Please create a new Accounting Dimension if required.")
frappe.throw(message)
def validate_fieldname_conflict(self):
conflicting_doctypes = []
for doctype in get_doctypes_with_dimensions():
meta = frappe.get_meta(doctype, cached=False)
if any(f.fieldname == self.fieldname for f in meta.get("fields")):
conflicting_doctypes.append(doctype)
if conflicting_doctypes:
frappe.msgprint(
_(
"Fieldname {0} already exists in the following doctypes: {1}. "
"A separate dimension field will not be added to these doctypes. "
"GL Entries will use the value of the existing field as the dimension value."
).format(
frappe.bold(self.fieldname),
", ".join(frappe.bold(d) for d in conflicting_doctypes),
),
title=_("Fieldname Conflict"),
indicator="orange",
)
def validate_dimension_defaults(self):
companies = []
for default in self.get("dimension_defaults"):

View File

@@ -38,16 +38,6 @@ frappe.ui.form.on("Accounts Settings", {
add_taxes_from_item_tax_template(frm) {
toggle_tax_settings(frm, "add_taxes_from_item_tax_template");
},
drop_ar_procedures: function (frm) {
frm.call({
doc: frm.doc,
method: "drop_ar_sql_procedures",
callback: function (r) {
frappe.show_alert(__("Procedures dropped"), 5);
},
});
},
});
function toggle_tax_settings(frm, field_name) {

View File

@@ -96,7 +96,6 @@
"receivable_payable_fetch_method",
"default_ageing_range",
"column_break_ntmi",
"drop_ar_procedures",
"legacy_section",
"ignore_is_opening_check_for_reporting",
"tab_break_dpet",
@@ -523,7 +522,7 @@
"fieldname": "receivable_payable_fetch_method",
"fieldtype": "Select",
"label": "Data Fetch Method",
"options": "Buffered Cursor\nUnBuffered Cursor\nRaw SQL"
"options": "Buffered Cursor\nUnBuffered Cursor"
},
{
"fieldname": "accounts_receivable_payable_tuning_section",
@@ -592,13 +591,6 @@
"fieldname": "column_break_ntmi",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.receivable_payable_fetch_method == \"Raw SQL\"",
"description": "Drops existing SQL Procedures and Function setup by Accounts Receivable report",
"fieldname": "drop_ar_procedures",
"fieldtype": "Button",
"label": "Drop Procedures"
},
{
"default": "0",
"fieldname": "fetch_valuation_rate_for_internal_transaction",
@@ -725,7 +717,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-04-13 15:30:28.729627",
"modified": "2026-05-18 12:16:33.679345",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -89,7 +89,7 @@ class AccountsSettings(Document):
make_payment_via_journal_entry: DF.Check
merge_similar_account_heads: DF.Check
over_billing_allowance: DF.Currency
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor", "Raw SQL"]
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
receivable_payable_remarks_length: DF.Int
reconciliation_queue_size: DF.Int
repost_allowed_types: DF.Table[RepostAllowedTypes]
@@ -209,13 +209,6 @@ class AccountsSettings(Document):
set_allow_on_submit_for_dimension_fields(doctypes)
@frappe.whitelist()
def drop_ar_sql_procedures(self):
from erpnext.accounts.report.accounts_receivable.accounts_receivable import InitSQLProceduresForAR
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")
def toggle_accounting_dimension_sections(hide):
accounting_dimension_doctypes = frappe.get_hooks("accounting_dimension_doctypes")

View File

@@ -70,6 +70,10 @@ frappe.ui.form.on("Journal Entry", {
},
refresh: function (frm) {
if (frm.doc.reversal_of && (frm.is_new() || frm.doc.docstatus == 0)) {
frm.set_read_only();
}
erpnext.toggle_naming_series();
if (frm.doc.docstatus > 0) {

View File

@@ -710,31 +710,12 @@ frappe.ui.form.on("Payment Entry", {
if (!frm.doc.paid_from_account_currency || !frm.doc.company) return;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.paid_from_account_currency == company_currency) {
frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from) {
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
let company_currency = frappe.get_doc(":Company", frm.doc.company)?.default_currency;
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
args: {
from_currency: frm.doc.paid_from_account_currency,
to_currency: company_currency,
transaction_date: frm.doc.posting_date,
},
callback: function (r, rt) {
frm.set_value("source_exchange_rate", r.message);
},
});
} else {
frm.events.set_current_exchange_rate(
frm,
"source_exchange_rate",
frm.doc.paid_from_account_currency,
company_currency
);
}
}
frm.events.set_current_exchange_rate(
frm,
"source_exchange_rate",
frm.doc.paid_from_account_currency,
company_currency
);
},
paid_to_account_currency: function (frm) {
@@ -766,49 +747,24 @@ frappe.ui.form.on("Payment Entry", {
posting_date: function (frm) {
frm.events.paid_from_account_currency(frm);
frm.events.paid_to_account_currency(frm);
},
source_exchange_rate: function (frm) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.paid_amount) {
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
// target exchange rate should always be same as source if both account currencies is same
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
} else if (company_currency == frm.doc.paid_to_account_currency) {
frm.set_value("received_amount", frm.doc.base_paid_amount);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
}
// set_unallocated_amount is called by below method,
// no need trigger separately
frm.events.set_total_allocated_amount(frm);
}
// Make read only if Accounts Settings doesn't allow stale rates
frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
},
target_exchange_rate: function (frm) {
frm.set_paid_amount_based_on_received_amount = true;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.received_amount) {
frm.set_value(
"base_received_amount",
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)
);
if (frm.doc.base_received_amount && frm.doc.source_exchange_rate) {
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
if (
!frm.doc.source_exchange_rate &&
frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
} else if (company_currency == frm.doc.paid_from_account_currency) {
frm.set_value("paid_amount", frm.doc.base_received_amount);
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
// target exchange rate should always be same as source if both account currencies is same
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
} else {
frm.set_value(
"paid_amount",
flt(frm.doc.base_paid_amount) / flt(frm.doc.source_exchange_rate)
);
}
// set_unallocated_amount is called by below method,
@@ -817,6 +773,32 @@ frappe.ui.form.on("Payment Entry", {
}
frm.set_paid_amount_based_on_received_amount = false;
// Make read only if Accounts Settings doesn't allow stale rates
frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
},
target_exchange_rate: function (frm) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.base_paid_amount && frm.doc.target_exchange_rate) {
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
if (
!frm.doc.source_exchange_rate &&
frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
} else {
frm.set_value(
"received_amount",
flt(frm.doc.base_received_amount) / flt(frm.doc.target_exchange_rate)
);
}
// set_unallocated_amount is called by below method,
// no need trigger separately
frm.events.set_total_allocated_amount(frm);
}
// Make read only if Accounts Settings doesn't allow stale rates
frm.set_df_property("target_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
},

View File

@@ -322,7 +322,7 @@
"reqd": 1
},
{
"depends_on": "doc.received_amount",
"depends_on": "eval:doc.received_amount;",
"fieldname": "base_received_amount",
"fieldtype": "Currency",
"label": "Received Amount (Company Currency)",
@@ -795,7 +795,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2026-03-09 17:15:30.453920",
"modified": "2026-05-15 13:31:01.166010",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -677,7 +677,7 @@ def validate_due_date_with_template(posting_date, due_date, bill_date, template_
if not default_due_date:
return
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
if getdate(default_due_date) != getdate(posting_date) and getdate(due_date) > getdate(default_due_date):
if frappe.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles():
party_type = "supplier" if doctype == "Purchase Invoice" else "customer"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,161 +0,0 @@
{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None, print_heading_template=None) -%}
{% if letter_head and not no_letterhead %}
<div class="letter-head">{{ letter_head }}</div>
{% endif %}
{% if print_heading_template %}
{{ frappe.render_template(print_heading_template, {"doc":doc}) }}
{% else %}
{% endif %}
{%- if doc.meta.is_submittable and doc.docstatus==2-%}
<div class="text-center" document-status="cancelled">
<h4 style="margin: 0px;">{{ _("CANCELLED") }}</h4>
</div>
{%- endif -%}
{%- endmacro -%}
{% for page in layout %}
<div class="page-break">
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
</div>
<style>
.taxes-section .order-taxes.mt-5{
margin-top: 0px !important;
}
.taxes-section .order-taxes .border-btm.pb-5{
padding-bottom: 0px !important;
}
.print-format label{
color: #74808b;
font-size: 12px;
margin-bottom: 4px;
}
</style>
{% if print_settings.repeat_header_footer %}
<div id="footer-html" class="visible-pdf">
{% if not no_letterhead and footer %}
<div class="letter-head-footer">
{{ footer }}
</div>
{% endif %}
<p class="text-center small page-number visible-pdf">
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
</p>
</div>
{% endif %}
<div class="row section-break" style="margin-bottom: 10px;">
<div class="col-xs-6 p-0">
<div class="col-xs-12 value text-uppercase"><b>{{ doc.customer }}</b></div>
<div class="col-xs-12">
{{ doc.address_display }}
</div>
<div class="col-xs-12">
{{ _("Contact: ")+doc.contact_display if doc.contact_display else '' }}
</div>
<div class="col-xs-12">
{{ _("Mobile: ")+doc.contact_mobile if doc.contact_mobile else '' }}
</div>
</div>
<div class="col-xs-3"></div>
<div class="col-xs-3" style="padding-left: 5px;">
<div>
<div><label>{{ _("Invoice ID") }}</label></div>
<div>{{ doc.name }}</div>
</div>
<div style="margin-top: 20px;">
<div><label>{{ _("Invoice Date") }}</label></div>
<div>{{ frappe.utils.format_date(doc.posting_date) }}</div>
</div>
<div style="margin-top: 20px;">
<div><label>{{ _("Due Date") }}</label></div>
<div>{{ frappe.utils.format_date(doc.due_date) }}</div>
</div>
</div>
</div>
<div class="section-break">
<table class="table table-bordered table-condensed mb-0" style="width: 100%; border-collapse: collapse; font-size: 12px;">
<colgroup>
<col style="width: 5%">
<col style="width: 45%">
<col style="width: 10%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr>
<th class="text-uppercase" style="text-align:center">{{ _("Sr") }}</th>
<th class="text-uppercase" style="text-align:center">{{ _("Details") }}</th>
<th class="text-uppercase" style="text-align:center">{{ _("Qty") }}</th>
<th class="text-uppercase" style="text-align:right">{{ _("Rate") }}</th>
<th class="text-uppercase" style="text-align:right">{{ _("Amount") }}</th>
</tr>
</thead>
{% for item in doc.items %}
<tr>
<td style="text-align:center">{{ loop.index }}</td>
<td>
<b>{{ item.item_code }}: {{ item.item_name }}</b>
{% if (item.description != item.item_name) %}
<br>{{ item.description }}
{% endif %}
</td>
<td style="text-align: center;">
{{ item.get_formatted("qty", 0) }}
{{ item.get_formatted("uom", 0) }}
</td>
<td style="text-align: right;">{{ item.get_formatted("net_rate", doc) }}</td>
<td style="text-align: right;">{{ item.get_formatted("net_amount", doc) }}</td>
</tr>
{% endfor %}
</table>
<!-- total -->
<div class="row">
<div class="col-xs-6">
<div>
<label>{{ _("Amount in Words") }}</label>
{{ doc.in_words }}
</div>
<div style="margin-top: 20px;">
<label>{{ _("Payment Status") }}</label>
{{ doc.status }}
</div>
</div>
<div class="col-xs-6">
<div class="row section-break">
<div class="col-xs-7"><div>{{ _("Sub Total") }}</div></div>
<div class="col-xs-5" style="text-align: right;">{{ doc.get_formatted("net_total", doc) }}</div>
</div>
<div>
{% for d in doc.taxes %}
{% if d.tax_amount %}
<div class="row">
<div class="col-xs-8"><div>{{ _(d.description) }}</div></div>
<div class="col-xs-4" style="text-align: right;">{{ d.get_formatted("tax_amount") }}</div>
</div>
{% endif %}
{% endfor %}
</div>
<div class="row">
<div class="col-xs-7"><div>{{ _("Total") }}</div></div>
<div class="col-xs-5" style="text-align: right;">{{ doc.get_formatted("grand_total", doc) }}</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="row important data-field">
<div class="col-xs-12"><label>{{ _("Terms and Conditions") }}: </label></div>
<div class="col-xs-12">{{ doc.terms if doc.terms else '' }}</div>
</div>
</div>
</div>
</div>
{% endfor %}

View File

@@ -1,32 +0,0 @@
{
"absolute_value": 0,
"align_labels_right": 0,
"creation": "2025-01-22 16:23:51.012200",
"css": "",
"custom_format": 0,
"default_print_language": "en",
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "",
"font_size": 14,
"idx": 0,
"line_breaks": 0,
"margin_bottom": 0.0,
"margin_left": 0.0,
"margin_right": 0.0,
"margin_top": 0.0,
"modified": "2025-01-22 16:23:51.012200",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Print",
"owner": "Administrator",
"page_number": "Hide",
"print_format_builder": 0,
"print_format_builder_beta": 0,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 0,
"standard": "Yes"
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1,227 @@
{% include "accounts/report/accounts_receivable/accounts_receivable.html" %}
<style type="text/css">
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table thead th {
background: #f8f8f8;
text-align: center;
font-size: 14px;
font-weight: 500;
color: #7c7c7c;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
padding: 6px 8px;
}
.report-table tbody td {
padding: 6px 8px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
vertical-align: top;
word-wrap: break-word;
font-size: 14px;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
.text-center { text-align: center; }
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
font-size: 14px;
display: flex;
justify-content: space-between;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<br>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Supplier") %}:</strong>
{%= (filters.party.length && filters.party.join(", ")) || __("All Parties") %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Report Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.report_date) %}
</div>
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
<th style="width: 8em; text-align: left;">{%= __("Date") %}</th>
<th style="text-align: left;">{%= __("Reference") %}</th>
{% if(filters.show_remarks) { %}
<th style="text-align: left;">{%= __("Remarks") %}</th>
{% } %}
<th style="width: 10em; text-align: right;">{%= __("Age (Days)") %}</th>
<th style="width: 10em; text-align: right;">{%= __("Invoiced Amount") %}</th>
<th style="width: 11em; text-align: right;">{%= __("Outstanding Amount") %}</th>
</tr>
</thead>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr>
<td class="text-left">{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td class="{% if(i == data.length - 1) { %}text-left text-bold{% } %}">
{% if(i == data.length - 1) { %}
{%= __("Total") %}
{% } else { %}
{%= data[i]["voucher_no"] %}
{% } %}
</td>
{% if(filters.show_remarks) { %}
<td class="text-left">
{% if(data[i]["remarks"] && data[i]["remarks"] != "No Remarks") { %}
{%= data[i]["remarks"] %}
{% } %}
</td>
{% } %}
<td class="text-right">{%= data[i]["age"] %}</td>
<td class="text-right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td class="text-right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
</tr>
{% } %}
</tbody>
</table>
</div>
&nbsp;
{% if(filters.show_future_payments) { %}
{%
var balance_row = data.slice(-1).pop();
var start = report.columns.findIndex(e => e.fieldname == 'age');
var currency = data[data.length - 1]["currency"];
var ranges = [
report.columns[start].label,
report.columns[start+1].label,
report.columns[start+2].label,
report.columns[start+3].label,
report.columns[start+4].label,
report.columns[start+5].label
];
%}
{% if(balance_row) { %}
<div class="report-table">
<table>
<thead>
<tr>
<th style="text-align: right;"></th>
{% for(var i = 0; i < ranges.length; i++) { %}
<th style="text-align: right;">{%= __(ranges[i]) %}</th>
{% } %}
<th style="text-align: right;">{%= __("Total") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row["age"], null, 2) %}</td>
<td class="text-right">{%= format_currency(balance_row["range1"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range2"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range3"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range4"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range5"], currency) %}</td>
<td class="text-right">{%= format_currency(flt(balance_row["outstanding"]), currency) %}</td>
</tr>
</tbody>
</table>
</div>
{% } %}
{% } %}
<p class="text-right">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1 +1,180 @@
{% include "accounts/report/accounts_receivable/accounts_receivable.html" %}
<style type="text/css">
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table thead th {
background: #f8f8f8;
font-size: 14px;
font-weight: 500;
color: #7c7c7c;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
padding: 6px 8px;
}
.report-table tbody td {
padding: 6px 8px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
vertical-align: top;
word-wrap: break-word;
font-size: 14px;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
font-size: 14px;
display: flex;
justify-content: space-between;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<br>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Supplier") %}:</strong>
{%= (filters.party && filters.party.length && filters.party.join(", ")) || __("All Parties") %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Ageing Based On") %}:</strong>
{%= __(filters.ageing_based_on) %}
</div>
<div>
<strong>{%= __("As on Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.report_date) %}
</div>
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
<th class="text-left">{%= __("Supplier") %}</th>
<th class="text-right">{%= __("Total Invoiced Amount") %}</th>
<th class="text-right">{%= __("Total Paid Amount") %}</th>
<th class="text-right">{%= __("Debit Note Amount") %}</th>
<th class="text-right">{%= __("Total Outstanding Amount") %}</th>
</tr>
</thead>
<tbody>
{% for (var i = 0, l = data.length; i < l; i++) {
var row = data[i];
if (!(row.party || row.is_total_row)) continue;
%}
<tr>
<td class="{% if (row.is_total_row) { %}text-bold{% } %}">
{% if (row.is_total_row) { %}
{%= __("Total") %}
{% } else { %}
{%= row.party %}
{% } %}
</td>
<td class="text-right">
{%= format_currency(row.invoiced, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.paid, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.debit_note, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.outstanding, row.currency) %}
</td>
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1,291 +1,225 @@
<style>
.print-format {
padding: 4mm;
font-size: 8.0pt !important;
}
.print-format td {
vertical-align:middle !important;
}
</style>
<style type="text/css">
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
<h4 class="text-center">
{% if (filters.party) { %}
{%= __(filters.party) %}
{% } %}
</h4>
<h6 class="text-center">
{% if (filters.tax_id) { %}
{%= __("Tax Id: ")%} {%= filters.tax_id %}
{% } %}
</h6>
<h5 class="text-center">
{%= __(filters.ageing_based_on) %}
{%= __("Until") %}
{%= frappe.datetime.str_to_user(filters.report_date) %}
</h5>
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
<div class="clearfix">
<div class="pull-left">
{% if(filters.payment_terms) { %}
<strong>{%= __("Payment Terms") %}:</strong> {%= filters.payment_terms %}
{% } %}
</div>
<div class="pull-right">
{% if(filters.credit_limit) { %}
<strong>{%= __("Credit Limit") %}:</strong> {%= format_currency(filters.credit_limit) %}
{% } %}
</div>
</div>
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
{% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop();
var start = report.columns.findIndex((elem) => (elem.fieldname == 'age'));
var range1 = report.columns[start].label;
var range2 = report.columns[start+1].label;
var range3 = report.columns[start+2].label;
var range4 = report.columns[start+3].label;
var range5 = report.columns[start+4].label;
var range6 = report.columns[start+5].label;
%}
{% if(balance_row) { %}
<table class="table table-bordered table-condensed">
<caption class="text-right">(Amount in {%= data[0]["currency"] || "" %})</caption>
<colgroup>
<col style="width: 30mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
</colgroup>
.report-table thead th {
background: #f8f8f8;
text-align: center;
font-size: 14px;
font-weight: 500;
color: #7c7c7c;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
padding: 6px 8px;
}
.report-table tbody td {
padding: 6px 8px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
vertical-align: top;
word-wrap: break-word;
font-size: 14px;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
.text-center { text-align: center; }
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-left { text-align: left; }
.text-bold { font-weight: 700;}
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
font-size: 14px;
display: flex;
justify-content: space-between;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<br>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Customer") %}:</strong>
{%= (filters.party.length && filters.party.join(", ")) || __("All Parties") %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Report Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.report_date) %}
</div>
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
<th>{%= __(" ") %}</th>
<th>{%= __(range1) %}</th>
<th>{%= __(range2) %}</th>
<th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th>
<th>{%= __(range6) %}</th>
<th>{%= __("Total") %}</th>
<th style="width: 8em; text-align: left;">{%= __("Date") %}</th>
<th style="text-align: left;">{%= __("Reference") %}</th>
{% if(filters.show_remarks) { %}
<th style="text-align: left;">{%= __("Remarks") %}</th>
{% } %}
<th style="width: 10em; text-align: right;">{%= __("Age (Days)") %}</th>
<th style="width: 10em; text-align: right;">{%= __("Invoiced Amount") %}</th>
<th style="width: 11em; text-align: right;">{%= __("Outstanding Amount") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">
{%= format_number(balance_row["age"], null, 2) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
</td>
</tr>
<td>{%= __("Future Payments") %}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-right">
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
</td>
<tr class="cvs-footer">
<th class="text-left">{%= __("Cheques Required") %}</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th class="text-right">
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
</tr>
</tbody>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr>
<td class="text-left">{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td class="{% if(i == data.length - 1) { %}text-left text-bold{% } %}">
{% if(i == data.length - 1) { %}
{%= __("Total") %}
{% } else { %}
{%= data[i]["voucher_no"] %}
{% } %}
</td>
{% if(filters.show_remarks) { %}
<td class="text-left">
{% if(data[i]["remarks"] && data[i]["remarks"] != "No Remarks") { %}
{%= data[i]["remarks"] %}
{% } %}
</td>
{% } %}
<td class="text-right">{%= data[i]["age"] %}</td>
<td class="text-right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td class="text-right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
</tr>
{% } %}
</tbody>
</table>
</div>
&nbsp;
{% if(filters.show_future_payments) { %}
{%
var balance_row = data.slice(-1).pop();
var start = report.columns.findIndex(e => e.fieldname == 'age');
var currency = data[data.length - 1]["currency"];
var ranges = [
report.columns[start].label,
report.columns[start+1].label,
report.columns[start+2].label,
report.columns[start+3].label,
report.columns[start+4].label,
report.columns[start+5].label
];
%}
{% if(balance_row) { %}
<div class="report-table">
<table>
<thead>
<tr>
<th style="text-align: right;"></th>
{% for(var i = 0; i < ranges.length; i++) { %}
<th style="text-align: right;">{%= __(ranges[i]) %}</th>
{% } %}
<th style="text-align: right;">{%= __("Total") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row["age"], null, 2) %}</td>
<td class="text-right">{%= format_currency(balance_row["range1"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range2"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range3"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range4"], currency) %}</td>
<td class="text-right">{%= format_currency(balance_row["range5"], currency) %}</td>
<td class="text-right">{%= format_currency(flt(balance_row["outstanding"]), currency) %}</td>
</tr>
</tbody>
</table>
</div>
{% } %}
{% } %}
<table class="table table-bordered">
<thead>
<tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
<th style="width: 10%">{%= __("Date") %}</th>
<th style="width: 4%">{%= __("Age (Days)") %}</th>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<th style="width: 14%">{%= __("Reference") %}</th>
<th style="width: 10%">{%= __("Sales Person") %}</th>
{% } else { %}
<th style="width: 24%">{%= __("Reference") %}</th>
{% } %}
{% if(!filters.show_future_payments) { %}
<th style="width: 20%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
{% if(!filters.show_future_payments) { %}
<th style="width: 10%; text-align: right">{%= __("Paid Amount") %}</th>
<th style="width: 10%; text-align: right">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 12%">{%= __("Customer LPO No.") %}</th>
{% } %}
<th style="width: 10%">{%= __("Future Payment Ref") %}</th>
<th style="width: 10%">{%= __("Future Payment Amount") %}</th>
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
{% } %}
{% } else { %}
<th style="width: 40%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
<th style="width: 15%">{%= __("Total Invoiced Amount") %}</th>
<th style="width: 15%">{%= __("Total Paid Amount") %}</th>
<th style="width: 15%">{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}</th>
<th style="width: 15%">{%= __("Total Outstanding Amount") %}</th>
{% } %}
</tr>
</thead>
<div class="show-filters">
{% if subtitle %}
{{ subtitle }}
<hr>
{% endif %}
</div>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
{% if(data[i]["party"]) { %}
<td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td style="text-align: right">{%= data[i]["age"] %}</td>
<td>
{% if(!filters.show_future_payments) { %}
{%= data[i]["voucher_type"] %}
<br>
{% } %}
{%= data[i]["voucher_no"] %}
</td>
<p class="text-right">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<td>{%= data[i]["sales_person"] %}</td>
{% } %}
{% if(!filters.show_future_payments) { %}
<td>
{% if(!filters.party?.length) { %}
{%= data[i]["party"] %}
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i]["customer_name"] %}
{% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
<br> {%= data[i]["supplier_name"] %}
{% } %}
{% } %}
<div>
{% if data[i]["remarks"] %}
{%= __("Remarks") %}:
{%= data[i]["remarks"] %}
{% } %}
</div>
</td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">
{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right">
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %}
{% } else { %}
<td></td>
{% if(!filters.show_future_payments) { %}
<td></td>
{% } %}
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<td></td>
{% } %}
<td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right">
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
{% } else { %}
{% if(data[i]["party"]|| "&nbsp;") { %}
{% if(!data[i]["is_total_row"]) { %}
<td>
{% if(!filters.party?.length) { %}
{%= data[i]["party"] %}
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i]["customer_name"] %}
{% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
<br> {%= data[i]["supplier_name"] %}
{% } %}
{% } %}
<br>{%= __("Remarks") %}:
{%= data[i]["remarks"] %}
</td>
{% } else { %}
<td><b>{%= __("Total") %}</b></td>
{% } %}
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
</tr>
{% } %}
</tbody>
</table>
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>
</div>

View File

@@ -131,8 +131,6 @@ class ReceivablePayableReport:
self.fetch_ple_in_buffered_cursor()
elif self.ple_fetch_method == "UnBuffered Cursor":
self.fetch_ple_in_unbuffered_cursor()
elif self.ple_fetch_method == "Raw SQL":
self.fetch_ple_in_sql_procedures()
# Build delivery note map against all sales invoices
self.build_delivery_note_map()
@@ -323,81 +321,6 @@ class ReceivablePayableReport:
row.paid -= amount
row.paid_in_account_currency -= amount_in_account_currency
def fetch_ple_in_sql_procedures(self):
self.proc = InitSQLProceduresForAR()
build_balance = f"""
begin not atomic
declare done boolean default false;
declare rec1 row type of `{self.proc._row_def_table_name}`;
declare ple cursor for {self.ple_query.get_sql()};
declare continue handler for not found set done = true;
open ple;
fetch ple into rec1;
while not done do
call {self.proc.init_procedure_name}(rec1);
fetch ple into rec1;
end while;
close ple;
set done = false;
open ple;
fetch ple into rec1;
while not done do
call {self.proc.allocate_procedure_name}(rec1);
fetch ple into rec1;
end while;
close ple;
end;
"""
frappe.db.sql(build_balance)
balances = frappe.db.sql(
f"""select
name,
voucher_type,
voucher_no,
party,
party_account `account`,
posting_date,
account_currency,
cost_center,
project,
sum(invoiced) `invoiced`,
sum(paid) `paid`,
sum(credit_note) `credit_note`,
sum(invoiced) - sum(paid) - sum(credit_note) `outstanding`,
sum(invoiced_in_account_currency) `invoiced_in_account_currency`,
sum(paid_in_account_currency) `paid_in_account_currency`,
sum(credit_note_in_account_currency) `credit_note_in_account_currency`,
sum(invoiced_in_account_currency) - sum(paid_in_account_currency) - sum(credit_note_in_account_currency) `outstanding_in_account_currency`
from `{self.proc._voucher_balance_name}` group by name order by posting_date;""",
as_dict=True,
)
for x in balances:
if self.filters.get("ignore_accounts"):
key = (x.voucher_type, x.voucher_no, x.party)
else:
key = (x.account, x.voucher_type, x.voucher_no, x.party)
_d = self.build_voucher_dict(x)
for field in [
"invoiced",
"paid",
"credit_note",
"outstanding",
"invoiced_in_account_currency",
"paid_in_account_currency",
"credit_note_in_account_currency",
"outstanding_in_account_currency",
"cost_center",
"project",
]:
_d[field] = x.get(field)
self.voucher_balance[key] = _d
def update_sub_total_row(self, row, party):
total_row = self.total_row_map.get(party)
@@ -1400,120 +1323,3 @@ def get_party_group_with_children(party, party_groups):
frappe.throw(_("{0}: {1} does not exist").format(group_dtype, d))
return list(set(all_party_groups))
class InitSQLProceduresForAR:
"""
Initialize SQL Procedures, Functions and Temporary tables to build Receivable / Payable report
"""
_varchar_type = get_definition("Data")
_currency_type = get_definition("Currency")
# Temporary Tables
_voucher_balance_name = "_ar_voucher_balance"
_voucher_balance_definition = f"""
create temporary table `{_voucher_balance_name}`(
name {_varchar_type},
voucher_type {_varchar_type},
voucher_no {_varchar_type},
party {_varchar_type},
party_account {_varchar_type},
posting_date date,
account_currency {_varchar_type},
cost_center {_varchar_type},
project {_varchar_type},
invoiced {_currency_type},
paid {_currency_type},
credit_note {_currency_type},
invoiced_in_account_currency {_currency_type},
paid_in_account_currency {_currency_type},
credit_note_in_account_currency {_currency_type}) engine=memory;
"""
_row_def_table_name = "_ar_ple_row"
_row_def_table_definition = f"""
create temporary table `{_row_def_table_name}`(
name {_varchar_type},
account {_varchar_type},
voucher_type {_varchar_type},
voucher_no {_varchar_type},
against_voucher_type {_varchar_type},
against_voucher_no {_varchar_type},
party_type {_varchar_type},
cost_center {_varchar_type},
project {_varchar_type},
party {_varchar_type},
posting_date date,
due_date date,
account_currency {_varchar_type},
amount {_currency_type},
amount_in_account_currency {_currency_type}) engine=memory;
"""
# Procedures
init_procedure_name = "ar_init_tmp_table"
init_procedure_sql = f"""
create procedure ar_init_tmp_table(in ple row type of `{_row_def_table_name}`)
begin
if not exists (select name from `{_voucher_balance_name}` where name = sha1(concat_ws(',', ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)))
then
insert into `{_voucher_balance_name}` values (sha1(concat_ws(',', ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)), ple.voucher_type, ple.voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency, ple.cost_center, ple.project, 0, 0, 0, 0, 0, 0);
end if;
end;
"""
allocate_procedure_name = "ar_allocate_to_tmp_table"
allocate_procedure_sql = f"""
create procedure ar_allocate_to_tmp_table(in ple row type of `{_row_def_table_name}`)
begin
declare invoiced {_currency_type} default 0;
declare invoiced_in_account_currency {_currency_type} default 0;
declare paid {_currency_type} default 0;
declare paid_in_account_currency {_currency_type} default 0;
declare credit_note {_currency_type} default 0;
declare credit_note_in_account_currency {_currency_type} default 0;
if ple.amount > 0 then
if (ple.voucher_type in ("Journal Entry", "Payment Entry") and (ple.voucher_no != ple.against_voucher_no)) then
set paid = -1 * ple.amount;
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
else
set invoiced = ple.amount;
set invoiced_in_account_currency = ple.amount_in_account_currency;
end if;
else
if ple.voucher_type in ("Sales Invoice", "Purchase Invoice") then
if (ple.voucher_no = ple.against_voucher_no) then
set paid = -1 * ple.amount;
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
else
set credit_note = -1 * ple.amount;
set credit_note_in_account_currency = -1 * ple.amount_in_account_currency;
end if;
else
set paid = -1 * ple.amount;
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
end if;
end if;
insert into `{_voucher_balance_name}` values (sha1(concat_ws(',', ple.account, ple.voucher_type, ple.voucher_no, ple.party)), ple.against_voucher_type, ple.against_voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency,'', '', invoiced, paid, 0, invoiced_in_account_currency, paid_in_account_currency, 0);
end;
"""
def __init__(self):
existing_procedures = frappe.db.get_routines()
if self.init_procedure_name not in existing_procedures:
frappe.db.sql(self.init_procedure_sql)
if self.allocate_procedure_name not in existing_procedures:
frappe.db.sql(self.allocate_procedure_sql)
frappe.db.sql(f"drop table if exists `{self._voucher_balance_name}`")
frappe.db.sql(self._voucher_balance_definition)
frappe.db.sql(f"drop table if exists `{self._row_def_table_name}`")
frappe.db.sql(self._row_def_table_definition)

View File

@@ -1 +1,180 @@
{% include "accounts/report/accounts_receivable/accounts_receivable.html" %}
<style type="text/css">
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table thead th {
background: #f8f8f8;
font-size: 14px;
font-weight: 500;
color: #7c7c7c;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
padding: 6px 8px;
}
.report-table tbody td {
padding: 6px 8px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
vertical-align: top;
word-wrap: break-word;
font-size: 14px;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
font-size: 14px;
display: flex;
justify-content: space-between;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<br>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Customer") %}:</strong>
{%= (filters.party && filters.party.length && filters.party.join(", ")) || __("All Parties") %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Ageing Based On") %}:</strong>
{%= __(filters.ageing_based_on) %}
</div>
<div>
<strong>{%= __("As on Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.report_date) %}
</div>
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
<th class="text-left">{%= __("Customer") %}</th>
<th class="text-right">{%= __("Total Invoiced Amount") %}</th>
<th class="text-right">{%= __("Total Paid Amount") %}</th>
<th class="text-right">{%= __("Credit Note Amount") %}</th>
<th class="text-right">{%= __("Total Outstanding Amount") %}</th>
</tr>
</thead>
<tbody>
{% for (var i = 0, l = data.length; i < l; i++) {
var row = data[i];
if (!(row.party || row.is_total_row)) continue;
%}
<tr>
<td class="{% if (row.is_total_row) { %}text-bold{% } %}">
{% if (row.is_total_row) { %}
{%= __("Total") %}
{% } else { %}
{%= row.party %}
{% } %}
</td>
<td class="text-right">
{%= format_currency(row.invoiced, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.paid, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.credit_note, row.currency) %}
</td>
<td class="text-right">
{%= format_currency(row.outstanding, row.currency) %}
</td>
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1 +1,224 @@
{% include "accounts/report/financial_statements.html" %}
{%
const report_columns = report
.get_columns_for_print()
.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(
__("Too many columns. Export the report and print it using a spreadsheet application.")
);
}
%}
<style>
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.financial-statements-important td { font-weight: bold; }
.financial-statements-blank-row td { height: 20px; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
display: flex;
justify-content: space-between;
font-size: 14px;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right {
text-align: right;
font-variant-numeric: tabular-nums;
}
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table th,
.report-table td {
padding: 6px 8px;
font-size: 14px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
}
.report-table thead th {
background: #f8f8f8;
font-weight: 500;
color: #7c7c7c;
}
.report-table tbody td {
vertical-align: top;
word-wrap: break-word;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
</div>
<div>
<strong>{%= __("Currency") %}:</strong>
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Period Based On") %}:</strong>
{%= filters.filter_based_on %}
</div>
{% if (filters.filter_based_on === "Fiscal Year") { %}
<div>
<strong>{%= __("Start Year") %}:</strong> {%= filters.from_fiscal_year %}
</div>
<div>
<strong>{%= __("End Year") %}:</strong> {%= filters.to_fiscal_year %}
</div>
{% } else if (filters.filter_based_on === "Date Range") { %}
<div>
<strong>{%= __("Start Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_start_date) %}
</div>
<div>
<strong>{%= __("End Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_end_date) %}
</div>
{% } %}
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const align = i === 0 ? "text-left" : "text-right";
%}
<th class="{%= align %}">
{%= col.label %}
</th>
{% } %}
</tr>
</thead>
<tbody>
{% for (let j = 0, k = data.length; j < k; j++) { %}
{%
const row = data[j];
let row_class = "";
if (!(row.parent_account || row.parent_section)) {
row_class = "financial-statements-important";
}
if (!(row.account_name || row.section)) {
row_class += " financial-statements-blank-row";
}
%}
<tr class="{%= row_class %}">
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const value = row[col.fieldname];
const align = i === 0 ? "text-left" : "text-right";
%}
<td class="{%= align %}">
{% if (i === 0) { %}
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
</span>
{% } else if (!is_null(value)) { %}
{%= frappe.format(value, col, {}, row) %}
{% } %}
</td>
{% } %}
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right text-muted">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1 +1,224 @@
{% include "accounts/report/financial_statements.html" %}
{%
const report_columns = report
.get_columns_for_print()
.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(
__("Too many columns. Export the report and print it using a spreadsheet application.")
);
}
%}
<style>
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.financial-statements-important td { font-weight: bold; }
.financial-statements-blank-row td { height: 20px; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
display: flex;
justify-content: space-between;
font-size: 14px;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right {
text-align: right;
font-variant-numeric: tabular-nums;
}
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table th,
.report-table td {
padding: 6px 8px;
font-size: 14px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
}
.report-table thead th {
background: #f8f8f8;
font-weight: 500;
color: #7c7c7c;
}
.report-table tbody td {
vertical-align: top;
word-wrap: break-word;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
</div>
<div>
<strong>{%= __("Currency") %}:</strong>
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Period Based On") %}:</strong>
{%= filters.filter_based_on %}
</div>
{% if (filters.filter_based_on === "Fiscal Year") { %}
<div>
<strong>{%= __("Start Year") %}:</strong> {%= filters.from_fiscal_year %}
</div>
<div>
<strong>{%= __("End Year") %}:</strong> {%= filters.to_fiscal_year %}
</div>
{% } else if (filters.filter_based_on === "Date Range") { %}
<div>
<strong>{%= __("Start Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_start_date) %}
</div>
<div>
<strong>{%= __("End Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_end_date) %}
</div>
{% } %}
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const align = i === 0 ? "text-left" : "text-right";
%}
<th class="{%= align %}">
{%= col.label %}
</th>
{% } %}
</tr>
</thead>
<tbody>
{% for (let j = 0, k = data.length; j < k; j++) { %}
{%
const row = data[j];
let row_class = "";
if (!(row.parent_account || row.parent_section)) {
row_class = "financial-statements-important";
}
if (!(row.account_name || row.section)) {
row_class += " financial-statements-blank-row";
}
%}
<tr class="{%= row_class %}">
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const value = row[col.fieldname];
const align = i === 0 ? "text-left" : "text-right";
%}
<td class="{%= align %}">
{% if (i === 0) { %}
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
</span>
{% } else if (!is_null(value)) { %}
{%= frappe.format(value, col, {}, row) %}
{% } %}
</td>
{% } %}
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right text-muted">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1,51 +1,114 @@
<!-- Modified on 25-11-2024
-->
<style type="text/css">
/* General styles for both screen display and print */
body, html {
margin-top: 10;
margin-top: 10px;
padding: 0;
width: 100%;
height: auto; /* Allow content to expand */
font-family: Arial, sans-serif; /* Example font */
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
/* Ensure consistent letter spacing across all media */
.title-letter-spacing {
letter-spacing: .2rem;
font-size: 15px;
font-weight: 600;
color: #171717;
}
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table thead th {
background: #f8f8f8;
text-align: center;
font-size: 14px;
font-weight: 500;
color: #7c7c7c;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
padding: 6px 8px;
}
.report-table tbody td {
padding: 6px 8px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
vertical-align: top;
word-wrap: break-word;
font-size: 14px;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
.date-col {
white-space: nowrap;
}
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-bold { font-weight: 700; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
color: #171717;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta .filter-row {
margin-bottom: 4px;
line-height: 1.5;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
/* Styles specific to printing and PDF generation */
@media print {
/* Set page size and margins for printing */
@page {
size: A4; /* Use fixed A4 page size */
size: A4;
margin-top: 10mm;
}
/* Force a page break before elements with the class "page-break" */
.page-break {
page-break-before: always;
margin-top: 10mm; /* Add some space after the break */
}
/* Ensure table headers repeat on each printed page */
thead {
display: table-header-group;
}
/* Ensure table footers repeat on each printed page */
tfoot {
display: table-footer-group;
}
th, td {
padding: 1px;
border: 1px solid black; /* Example border for clarity */
tr {
page-break-inside: avoid;
}
/* Hide elements that should not appear in print (optional) */
.no-print {
display: none !important;
}
@@ -53,129 +116,154 @@
</style>
<br>
<div style="font-family:Arial">
<div>
<div class="title-letter-spacing" style="text-align:center; font-size:15px; text-decoration:underline;">
<b>
{%= __("STATEMENT OF ACCOUNTS") %}<br>
{% if (filters.party_name) { %}
<br>{%= filters.party_name %}
{% } else if (filters.party && filters.party.length) { %}
<br>{%= filters.party %}
{% } else if (filters.account) { %}
<br>{%= filters.account %}
{% } else { %}
<br>{%= __("All Parties ") %}
{% } %}
</b>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __("Statement Of Accounts") %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div class="filter-row">
<strong>{%= __("Customer") %}:</strong>
{%=
(filters.party.length && filters.party.join(", ")) || filters.party_name || "All Parties"
%}
</div>
</div>
<div class="right text-right">
<div class="filter-row">
<strong>{%= __("Statement Period") %}:</strong>
{%= __("{0} to {1}", [
frappe.datetime.str_to_user(filters.from_date),
frappe.datetime.str_to_user(filters.to_date)
]) %}
</div>
</div>
</div>
<div style="text-align:center; font-size:13px;">
<b>
{%= __("{0} to {1}", [frappe.datetime.str_to_user(filters.from_date), frappe.datetime.str_to_user(filters.to_date)]) %}<br><br>
</b>
</div>
</div>
<div class="show-filters">
{% if subtitle %}
{{ subtitle }}
<hr>
{% endif %}
</div>
<table style="width:100%; font-size: 11px">
<thead>
<tr class="title-letter-spacing" style="text-align: center; font-weight:bold">
<td style="border: 1.5px solid black; width: 7em">{%= __("Date").toLocaleUpperCase() %}</td>
<td style="border: 1.5px solid black">{%= __("Particulars").toLocaleUpperCase() %}</td>
{% if(filters.show_remarks) { %}
<td style="border: 1.5px solid black">{%= __("Remarks").toLocaleUpperCase() %}</td>
{% } %}
<td style="border: 1.5px solid black; width: 9em">{%= __("Debit").toLocaleUpperCase() %}</td>
<td style="border: 1.5px solid black; width: 9em">{%= __("Credit").toLocaleUpperCase() %}</td>
<td style="border: 1.5px solid black; width: 10.2em">{%= __("Balance").toLocaleUpperCase() %}</td>
</tr>
</thead>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr style="border-bottom: 1px solid black">
{% if(data[i].posting_date) { %}
<td style="text-align: center; border: 1px dotted black">
{%= frappe.datetime.str_to_user(data[i].posting_date) %}
</td>
<td style="border-right: 1px dotted black">
{%= data[i].voucher_type %} {%= data[i].voucher_no %}
{% if(!(filters.party || filters.account)) { %}
{%= data[i].party || data[i].account %}
{% } %}<br>
{% if(data[i].bill_no) { %}
{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}
{% } %}
</td>
{% if(filters.show_remarks) { %}
<td style="border-right: 1px dotted black; font-size: 10px">
{% if(data[i].remarks != "No Remarks" && data[i].remarks != "") { %}
{%= __("Remarks") %}: {%= data[i].remarks %}<br>
{% } %}
</td>
{% } %}
<td style="text-align: right; border-right: 1px dotted black">
{% if data[i].debit != 0 %}
{%= format_currency(data[i].debit, filters.presentation_currency) %}
{% } %}
</td>
<td style="text-align: right; border-right: 1px dotted black">
{% if data[i].credit != 0 %}
{%= format_currency(data[i].credit, filters.presentation_currency) %}
{% } %}
</td>
{% } else { %}
<td style="text-align: center; border: 1px dotted black">
{% if(i == 0) { %}
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
<th style="width: 8em; text-align: left;">{%= __("Date") %}</th>
<th style="text-align: left;">{%= __("Voucher Details") %}</th>
{% if(filters.show_remarks) { %}
<th style="text-align: left;">{%= __("Remarks") %}</th>
{% } %}
<th style="width: 10em; text-align: right;">{%= __("Debit") %}</th>
<th style="width: 10em; text-align: right;">{%= __("Credit") %}</th>
<th style="width: 10em; text-align: right;">{%= __("Balance") %}</th>
</tr>
</thead>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
{% var row = data[i]; %}
{% var is_entry = row.posting_date; %}
{% var is_last = i == l-1; %}
{% var is_second_last = i == l-2; %}
<tr>
<td class="text-left date-col">
{% if(is_entry) { %}
{%= frappe.datetime.str_to_user(row.posting_date) %}
{% } else if(i == 0) { %}
{%= frappe.datetime.str_to_user(filters.from_date) %}
{% } %}
</td>
<td style="text-align: left; border-right: 1px dotted black"><b>
{% if(i == l-2) { %}
{%= __("Total") %}
<td class="{% if(!is_entry) { %}text-left text-bold{% } %}">
{% if(is_entry) { %}
{%= row.voucher_type %} {%= row.voucher_no %}
{% if(!(filters.party || filters.account)) { %}
<div style="margin-top: 2px;">
{%= row.party || row.account %}
</div>
{% } %}
{% if(row.bill_no) { %}
<div style="margin-top: 2px;">
{%= __("Supplier Invoice No") %}: {%= row.bill_no %}
</div>
{% } %}
{% } else { %}
{% if(i == l-1) { %}
{% if(is_second_last) { %}
{%= __("Total") %}
{% } else if(is_last) { %}
{%= __("Closing [Opening + Total] ") %}
{% } else { %}
{%= frappe.format(data[i].account, {fieldtype: "Link"}) || "&nbsp;" %}
{% } %}
{% } %}</b>
</td>
{% if(filters.show_remarks) { %} <td style="text-align: left; border-right: 1px dotted black"></td>{% } %}
<td style="text-align: right; border-right: 1px dotted black">
{% if(i != 0){ %}
{% if(i != l-1){ %}
{%= data[i].account && format_currency(data[i].debit, filters.presentation_currency) %}
{%= frappe.format(row.account, {fieldtype: "Link"}) || "&nbsp;" %}
{% } %}
{% } %}
</td>
<td style="text-align: right; border-right: 1px dotted black">
{% if(i != 0){ %}
{% if(i != l-1){ %}
{%= data[i].account && format_currency(data[i].credit, filters.presentation_currency) %}
{% } %}
{% if(filters.show_remarks) { %}
<td class="text-left">
{% if(is_entry && row.remarks && row.remarks != "No Remarks") { %}
{%= row.remarks %}
{% } %}
</td>
{% } %}
{% if(i == l-1) { %}
<td style="text-align: right; font-weight:bold; border-right: 1px dotted black">
{%= format_currency(data[i].balance, filters.presentation_currency) %}
{% if(data[i].balance < 0){ %}Cr{% } %}
{% if(data[i].balance > 0){ %}Dr{% } %}
</td>
{% } else { %}
<td style="text-align: right; border-right: 1px dotted black">
{% if(i != l-2) { %}
{%= format_currency(data[i].balance, filters.presentation_currency) %}
{% } %}
</td>
{% } %}
</tr>
{% endfor%}
</tbody>
</table>
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>
</div>
<td class="text-right">
{% if(is_entry) { %}
{% if(row.debit != 0) { %}
{%= format_currency(row.debit, filters.presentation_currency) %}
{% } %}
{% } else if(i != 0 && !is_last) { %}
{%= row.account && format_currency(row.debit, filters.presentation_currency) %}
{% } %}
</td>
<td class="text-right">
{% if(is_entry) { %}
{% if(row.credit != 0) { %}
{%= format_currency(row.credit, filters.presentation_currency) %}
{% } %}
{% } else if(i != 0 && !is_last) { %}
{%= row.account && format_currency(row.credit, filters.presentation_currency) %}
{% } %}
</td>
<td class="text-right {% if(is_last) { %}text-bold{% } %}">
{% if(is_last) { %}
{%= format_currency(row.balance, filters.presentation_currency) %}
{% if(row.balance < 0) { %} Cr{% } %}
{% if(row.balance > 0) { %} Dr{% } %}
{% } else { %}
{%= format_currency(row.balance, filters.presentation_currency) %}
{% } %}
</td>
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -177,10 +177,16 @@ frappe.query_reports["General Ledger"] = {
fieldtype: "Check",
default: 1,
},
{
fieldname: "disable_opening_balance_calculation",
label: __("Disable Opening Balance Calculation"),
fieldtype: "Check",
},
{
fieldname: "show_opening_entries",
label: __("Show Opening Entries"),
fieldtype: "Check",
depends_on: "eval: !doc.disable_opening_balance_calculation",
},
{
fieldname: "include_default_book_entries",

View File

@@ -283,7 +283,15 @@ def get_conditions(filters):
if filters.get("party"):
conditions.append("party in %(party)s")
if not (
if filters.get("disable_opening_balance_calculation"):
if not ignore_is_opening:
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
else:
conditions.append("posting_date >=%(from_date)s")
# opening balance calculation is done only if filtered on account/party
# so from_date filter is not applied
elif not (
filters.get("account")
or filters.get("party")
or filters.get("categorize_by") in ["Categorize by Account", "Categorize by Party"]
@@ -417,7 +425,13 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
# Opening for filtered account
add_total_to_data(totals, "opening")
if filters.get("categorize_by") != "Categorize by Voucher (Consolidated)":
if not filters.get("categorize_by"):
all_entries = []
for acc_dict in gle_map.values():
all_entries.extend(acc_dict.entries)
data += all_entries
elif filters.get("categorize_by") != "Categorize by Voucher (Consolidated)":
set_opening_closing = (not filters.get("categorize_by") and not filters.get("voucher_no")) or (
filters.get("categorize_by") and filters.get("categorize_by") != "Categorize by Voucher"
)
@@ -483,7 +497,6 @@ def initialize_gle_map(gl_entries, filters):
totals=get_totals_dict(),
entries=[],
)
return gle_map
@@ -548,7 +561,11 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
gle.remarks = _(gle.remarks)
gle.party_type = _(gle.party_type)
if gle.posting_date < from_date or (cstr(gle.is_opening) == "Yes" and not show_opening_entries):
if gle.posting_date < from_date or (
cstr(gle.is_opening) == "Yes"
and not show_opening_entries
and not filters.disable_opening_balance_calculation
):
if not group_by_voucher_consolidated:
update_value_in_dict(gle_map[group_by_value].totals, "opening", gle, True)
update_value_in_dict(gle_map[group_by_value].totals, "closing", gle, True)

View File

@@ -812,19 +812,11 @@ class GrossProfitGenerator:
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, row.item_row, item_code
)
elif self.delivery_notes.get((row.parent, row.item_code), None):
# check if Invoice has delivery notes
dn = self.delivery_notes.get((row.parent, row.item_code))
parenttype, parent, item_row, dn_warehouse = (
"Delivery Note",
dn["delivery_note"],
dn["item_row"],
dn["warehouse"],
)
my_sle = self.get_stock_ledger_entries(item_code, dn_warehouse)
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, item_row, item_code
)
elif row.item_row and self.delivery_notes.get(row.item_row):
dn = self.delivery_notes[row.item_row]
if flt(dn.total_qty):
return flt(row.qty) * flt(dn.total_incoming_value) / flt(dn.total_qty)
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
elif row.sales_order and row.so_detail:
incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
if incoming_amount:
@@ -1076,25 +1068,29 @@ class GrossProfitGenerator:
def get_delivery_notes(self):
self.delivery_notes = frappe._dict({})
if self.si_list:
from frappe.query_builder.functions import Sum
invoices = [x.parent for x in self.si_list]
dni = qb.DocType("Delivery Note Item")
delivery_notes = (
qb.from_(dni)
.select(
dni.against_sales_invoice.as_("sales_invoice"),
dni.item_code,
dni.warehouse,
dni.parent.as_("delivery_note"),
dni.name.as_("item_row"),
dni.si_detail,
Sum(dni.stock_qty * dni.incoming_rate).as_("total_incoming_value"),
Sum(dni.stock_qty).as_("total_qty"),
)
.where((dni.docstatus == 1) & (dni.against_sales_invoice.isin(invoices)))
.groupby(dni.against_sales_invoice, dni.item_code)
.orderby(dni.creation, order=Order.desc)
.where(
(dni.docstatus == 1)
& (dni.against_sales_invoice.isin(invoices))
& (dni.si_detail.isnotnull())
& (dni.si_detail != "")
)
.groupby(dni.si_detail)
.run(as_dict=True)
)
for entry in delivery_notes:
self.delivery_notes[(entry.sales_invoice, entry.item_code)] = entry
self.delivery_notes[entry.si_detail] = entry
def group_items_by_invoice(self):
"""

View File

@@ -1 +1,224 @@
{% include "accounts/report/financial_statements.html" %}
{%
const report_columns = report
.get_columns_for_print()
.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(
__("Too many columns. Export the report and print it using a spreadsheet application.")
);
}
%}
<style>
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.financial-statements-important td { font-weight: bold; }
.financial-statements-blank-row td { height: 20px; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
display: flex;
justify-content: space-between;
font-size: 14px;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right {
text-align: right;
font-variant-numeric: tabular-nums;
}
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table th,
.report-table td {
padding: 6px 8px;
font-size: 14px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
}
.report-table thead th {
background: #f8f8f8;
font-weight: 500;
color: #7c7c7c;
}
.report-table tbody td {
vertical-align: top;
word-wrap: break-word;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
</div>
<div>
<strong>{%= __("Currency") %}:</strong>
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("Period Based On") %}:</strong>
{%= filters.filter_based_on %}
</div>
{% if (filters.filter_based_on === "Fiscal Year") { %}
<div>
<strong>{%= __("Start Year") %}:</strong> {%= filters.from_fiscal_year %}
</div>
<div>
<strong>{%= __("End Year") %}:</strong> {%= filters.to_fiscal_year %}
</div>
{% } else if (filters.filter_based_on === "Date Range") { %}
<div>
<strong>{%= __("Start Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_start_date) %}
</div>
<div>
<strong>{%= __("End Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.period_end_date) %}
</div>
{% } %}
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const align = i === 0 ? "text-left" : "text-right";
%}
<th class="{%= align %}">
{%= col.label %}
</th>
{% } %}
</tr>
</thead>
<tbody>
{% for (let j = 0, k = data.length; j < k; j++) { %}
{%
const row = data[j];
let row_class = "";
if (!(row.parent_account || row.parent_section)) {
row_class = "financial-statements-important";
}
if (!(row.account_name || row.section)) {
row_class += " financial-statements-blank-row";
}
%}
<tr class="{%= row_class %}">
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const value = row[col.fieldname];
const align = i === 0 ? "text-left" : "text-right";
%}
<td class="{%= align %}">
{% if (i === 0) { %}
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
</span>
{% } else if (!is_null(value)) { %}
{%= frappe.format(value, col, {}, row) %}
{% } %}
</td>
{% } %}
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right text-muted">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -1 +1,215 @@
{% include "accounts/report/financial_statements.html" %}
{%
const report_columns = report
.get_columns_for_print()
.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(
__("Too many columns. Export the report and print it using a spreadsheet application.")
);
}
%}
<style>
body, html {
margin-top: 10px;
padding: 0;
width: 100%;
height: auto;
font-family: Inter, sans-serif;
font-size: 14px;
line-height: 21px;
color: #171717;
}
.title-letter-spacing {
font-size: 15px;
font-weight: 600;
color: #171717;
}
.financial-statements-important td { font-weight: bold; }
.financial-statements-blank-row td { height: 20px; }
.report-meta {
margin: 10px 0 14px;
padding: 8px 10px;
display: flex;
justify-content: space-between;
font-size: 14px;
}
.report-meta .left,
.report-meta .right {
display: flex;
flex-direction: column;
}
.report-meta strong {
color: #7c7c7c;
font-weight: 500;
}
.report-subtitle {
margin: 10px 0 14px;
}
.text-center { text-align: center; }
.text-right {
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.text-left { text-align: left; }
.text-bold { font-weight: 700; }
.report-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.report-table th,
.report-table td {
padding: 6px 8px;
font-size: 14px;
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
}
.report-table thead th {
background: #f8f8f8;
font-weight: 500;
color: #7c7c7c;
}
.report-table tbody td.text-left {
vertical-align: top;
word-wrap: break-word;
}
.report-table thead th:first-child {
border-left: 1px solid #ededed;
}
.report-table thead th:last-child {
border-right: 1px solid #ededed;
}
.report-table tbody tr:last-child td {
border-bottom: none;
}
@media print {
@page {
size: A4;
margin-top: 10mm;
}
thead { display: table-header-group; }
tr { page-break-inside: avoid; }
}
</style>
<div>
<div class="text-center" style="margin-bottom: 12px;">
<div class="title-letter-spacing">
{%= __(report.report_name) %}
</div>
</div>
{% if (subtitle && subtitle.trim()) { %}
<div class="report-subtitle">
{{ subtitle }}
</div>
{% } else { %}
<div class="report-meta">
<div class="left">
<div>
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
</div>
<div>
<strong>{%= __("Currency") %}:</strong>
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
</div>
</div>
<div class="right text-right">
<div>
<strong>{%= __("From Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.from_date) %}
</div>
<div>
<strong>{%= __("To Date") %}:</strong>
{%= frappe.datetime.str_to_user(filters.to_date) %}
</div>
</div>
</div>
{% } %}
<div class="report-table">
<table>
<thead>
<tr>
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const align = i === 0 ? "text-left" : "text-right";
const styling = i === 0 ? "" : "width: 9em";
%}
<th class="{%= align %}" style= "{%= styling%}">
{%= col.label %}
</th>
{% } %}
</tr>
</thead>
<tbody>
{% for (let j = 0, k = data.length; j < k; j++) { %}
{%
const row = data[j];
let row_class = "";
if (!(row.parent_account || row.parent_section)) {
row_class = "financial-statements-important";
}
if (!(row.account_name || row.section)) {
row_class += " financial-statements-blank-row";
}
%}
<tr class="{%= row_class %}">
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
{%
const col = report_columns[i];
const value = row[col.fieldname];
const align = i === 0 ? "text-left" : "text-right";
%}
<td class="{%= align %}">
{% if (i === 0) { %}
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
</span>
{% } else if (!is_null(value)) { %}
{%= frappe.format(value, col, {}, row) %}
{% } %}
</td>
{% } %}
</tr>
{% } %}
</tbody>
</table>
</div>
<p class="text-right text-muted">
{%= __("Printed on {0}", [
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
]) %}
</p>
</div>

View File

@@ -14,7 +14,7 @@
"for_user": "",
"hide_custom": 0,
"icon": "table",
"idx": 0,
"idx": 1,
"indicator_color": "",
"is_hidden": 0,
"label": "Financial Reports",
@@ -266,13 +266,13 @@
"type": "Link"
}
],
"modified": "2025-12-24 12:49:25.266357",
"modified": "2026-05-18 09:49:45.138296",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Financial Reports",
"number_cards": [],
"owner": "Administrator",
"parent_page": "Accounting",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",

View File

@@ -1977,7 +1977,7 @@ def create_asset_category(enable_cwip=1):
asset_category.insert()
def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0):
def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0, asset_category=None):
meta = frappe.get_meta("Asset")
naming_series = meta.get_field("naming_series").options.splitlines()[0] or "ACC-ASS-.YYYY.-"
try:
@@ -1987,7 +1987,7 @@ def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_ass
"item_code": item_code or "Macbook Pro",
"item_name": "Macbook Pro",
"description": "Macbook Pro Retina Display",
"asset_category": "Computers",
"asset_category": asset_category or "Computers",
"item_group": "All Item Groups",
"stock_uom": "Nos",
"is_stock_item": 0,

View File

@@ -2,10 +2,59 @@
// For license information, please see license.txt
frappe.ui.form.on("Buying Settings", {
// refresh: function(frm) {
// }
refresh(frm) {
if (!frm.naming_controller) frm.naming_controller = new erpnext.NamingSeriesController(frm);
const display = frm.doc.supp_master_name === "Naming Series";
frm.set_df_property("naming_series_details", "hidden", !display);
frm.set_df_property("configure", "hidden", !display);
if (display) {
frm.naming_controller.load_master_series("Supplier", "naming_series_details");
}
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
},
supp_master_name(frm) {
const display = frm.doc.supp_master_name === "Naming Series";
frm.set_df_property("naming_series_details", "hidden", !display);
frm.set_df_property("configure", "hidden", !display);
if (display) {
frm.naming_controller.load_master_series("Supplier", "naming_series_details");
} else {
frm.doc.naming_series_details = "";
frm.refresh_field("naming_series_details");
}
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
},
configure(frm) {
frm.naming_controller.show_naming_series_dialog("Supplier", ({ naming_series_options }) => {
frm.doc.naming_series_details = naming_series_options;
frm.refresh_field("naming_series_details");
});
},
});
function get_transactions(frm) {
const transactions = [
{ label: __("Supplier"), doctype: "Supplier" },
{ label: __("Material Request"), doctype: "Material Request" },
{ label: __("Request for Quotation"), doctype: "Request for Quotation" },
{ label: __("Purchase Order"), doctype: "Purchase Order" },
{ label: __("Purchase Invoice"), doctype: "Purchase Invoice" },
{ label: __("Purchase Receipt"), doctype: "Purchase Receipt" },
];
if (frm.doc.supp_master_name !== "Naming Series") {
return transactions.filter((t) => t.doctype !== "Supplier");
}
return transactions;
}
frappe.tour["Buying Settings"] = [
{
fieldname: "supp_master_name",

View File

@@ -6,44 +6,51 @@
"engine": "InnoDB",
"field_order": [
"supplier_and_price_defaults_section",
"supplier_defaults_section",
"supp_master_name",
"supplier_group",
"buying_price_list",
"naming_series_details",
"configure",
"column_break_4",
"supplier_group",
"pricing_tab",
"buying_price_list",
"section_break_vwgg",
"maintain_same_rate",
"column_break_lwxs",
"maintain_same_rate_action",
"role_to_override_stop_action",
"section_break_xmlt",
"po_required",
"blanket_order_allowance",
"column_break_sbwq",
"pr_required",
"project_update_frequency",
"transaction_settings_section",
"column_break_fcyl",
"set_landed_cost_based_on_purchase_invoice_rate",
"allow_zero_qty_in_supplier_quotation",
"use_transaction_date_exchange_rate",
"allow_zero_qty_in_request_for_quotation",
"allow_negative_rates_for_items",
"po_required",
"pr_required",
"project_update_frequency",
"column_break_12",
"maintain_same_rate",
"allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice",
"allow_negative_rates_for_items",
"set_valuation_rate_for_rejected_materials",
"disable_last_purchase_rate",
"show_pay_button",
"purchase_invoice_settings_section",
"bill_for_rejected_quantity_in_purchase_invoice",
"use_transaction_date_exchange_rate",
"set_landed_cost_based_on_purchase_invoice_rate",
"zero_quantity_line_items_section",
"allow_zero_qty_in_supplier_quotation",
"allow_zero_qty_in_request_for_quotation",
"allow_zero_qty_in_purchase_order",
"blanket_order_section",
"blanket_order_allowance",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
"over_transfer_allowance",
"validate_consumed_qty",
"section_break_xcug",
"auto_create_subcontracting_order",
"column_break_izrr",
"auto_create_purchase_receipt",
"request_for_quotation_tab",
"fixed_email"
"fixed_email",
"document_naming_tab",
"transaction_naming_html"
],
"fields": [
{
@@ -54,6 +61,7 @@
"options": "Supplier Name\nNaming Series\nAuto Name"
},
{
"documentation_url": "https://docs.frappe.io/erpnext/buying-settings#2-default-supplier-group",
"fieldname": "supplier_group",
"fieldtype": "Link",
"label": "Default Supplier Group",
@@ -68,26 +76,27 @@
{
"fieldname": "po_required",
"fieldtype": "Select",
"label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
"label": "Is Purchase Order required for Purchase Invoice & Receipt creation?",
"options": "No\nYes"
},
{
"fieldname": "pr_required",
"fieldtype": "Select",
"label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
"label": "Is Purchase Receipt required for Purchase Invoice creation?",
"options": "No\nYes"
},
{
"default": "0",
"description": "Warn or stop if Item rate is changed in Purchase Invoice or Purchase Receipt generated from a Purchase Order.",
"fieldname": "maintain_same_rate",
"fieldtype": "Check",
"label": "Maintain Same Rate Throughout the Purchase Cycle"
"label": "Maintain same rate throughout the purchase cycle"
},
{
"default": "0",
"fieldname": "allow_multiple_items",
"fieldtype": "Check",
"label": "Allow Item To Be Added Multiple Times in a Transaction"
"label": "Allow Item to be added multiple times in a transaction"
},
{
"fieldname": "subcontract",
@@ -96,9 +105,10 @@
},
{
"default": "BOM",
"documentation_url": "https://docs.frappe.io/erpnext/buying-settings#1-backflush-raw-materials-of-subcontract-based-on",
"fieldname": "backflush_raw_materials_of_subcontract_based_on",
"fieldtype": "Select",
"label": "Backflush Raw Materials of Subcontract Based On",
"label": "Backflush raw materials of subcontract based on",
"options": "BOM\nMaterial Transferred for Subcontract"
},
{
@@ -108,25 +118,21 @@
"fieldtype": "Float",
"label": "Over Transfer Allowance (%)"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"default": "Stop",
"depends_on": "maintain_same_rate",
"description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
"fieldname": "maintain_same_rate_action",
"fieldtype": "Select",
"label": "Action If Same Rate is Not Maintained",
"label": "Action if same rate is not maintained",
"mandatory_depends_on": "maintain_same_rate",
"options": "Stop\nWarn"
},
{
"depends_on": "eval:doc.maintain_same_rate_action == 'Stop'",
"depends_on": "maintain_same_rate",
"fieldname": "role_to_override_stop_action",
"fieldtype": "Link",
"label": "Role Allowed to Override Stop Action",
"label": "Role allowed to override stop action",
"options": "Role"
},
{
@@ -134,12 +140,12 @@
"description": "If checked, Rejected Quantity will be included while making Purchase Invoice from Purchase Receipt.",
"fieldname": "bill_for_rejected_quantity_in_purchase_invoice",
"fieldtype": "Check",
"label": "Bill for Rejected Quantity in Purchase Invoice"
"label": "Bill for rejected quantity in Purchase Invoice"
},
{
"fieldname": "supplier_and_price_defaults_section",
"fieldtype": "Tab Break",
"label": "Naming Series and Price Defaults"
"label": "Defaults"
},
{
"fieldname": "column_break_4",
@@ -156,16 +162,17 @@
},
{
"default": "0",
"description": "Prevents the system from automatically using the rate from the last purchase transaction when creating new purchase orders or transactions.",
"fieldname": "disable_last_purchase_rate",
"fieldtype": "Check",
"label": "Disable Last Purchase Rate"
"label": "Disable last purchase rate"
},
{
"default": "1",
"depends_on": "eval: frappe.boot.versions && frappe.boot.versions.payments",
"fieldname": "show_pay_button",
"fieldtype": "Check",
"label": "Show Pay Button in Purchase Order Portal"
"label": "Show pay button in Purchase Order portal"
},
{
"default": "0",
@@ -193,30 +200,25 @@
"fieldname": "section_break_xcug",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_izrr",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "Subcontracting Order (Draft) will be auto-created on submission of Purchase Order.",
"fieldname": "auto_create_subcontracting_order",
"fieldtype": "Check",
"label": "Auto Create Subcontracting Order"
"label": "Auto create Subcontracting Order"
},
{
"default": "0",
"description": "Purchase Receipt (Draft) will be auto-created on submission of Subcontracting Receipt.",
"fieldname": "auto_create_purchase_receipt",
"fieldtype": "Check",
"label": "Auto Create Purchase Receipt"
"label": "Auto create Purchase Receipt"
},
{
"default": "Each Transaction",
"description": "How often should Project be updated of Total Purchase Cost ?",
"fieldname": "project_update_frequency",
"fieldtype": "Select",
"label": "Update frequency of Project",
"label": "How often should project be updated of Total Purchase Cost ?",
"options": "Each Transaction\nManual"
},
{
@@ -240,14 +242,6 @@
"fieldtype": "Check",
"label": "Allow Supplier Quotation with Zero Quantity"
},
{
"fieldname": "section_break_xmlt",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_sbwq",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_fcyl",
"fieldtype": "Column Break"
@@ -258,7 +252,7 @@
"description": "If enabled, the system will generate an accounting entry for materials rejected in the Purchase Receipt.",
"fieldname": "set_valuation_rate_for_rejected_materials",
"fieldtype": "Check",
"label": "Set Valuation Rate for Rejected Materials"
"label": "Set valuation rate for rejected Materials"
},
{
"fieldname": "request_for_quotation_tab",
@@ -279,23 +273,77 @@
"description": "Raw materials consumed qty will be validated based on FG BOM required qty",
"fieldname": "validate_consumed_qty",
"fieldtype": "Check",
"label": "Validate Consumed Qty (as per BOM)"
"label": "Validate consumed quantity (as per BOM)"
},
{
"default": "0",
"fieldname": "allow_negative_rates_for_items",
"fieldtype": "Check",
"label": "Allow Negative rates for Items"
"label": "Allow negative rates for Items"
},
{
"fieldname": "supplier_defaults_section",
"fieldtype": "Section Break",
"label": "Supplier Defaults"
},
{
"fieldname": "section_break_vwgg",
"fieldtype": "Section Break"
},
{
"fieldname": "blanket_order_section",
"fieldtype": "Section Break",
"label": "Blanket Orders"
},
{
"fieldname": "zero_quantity_line_items_section",
"fieldtype": "Section Break",
"label": "Zero-Quantity Line Items"
},
{
"fieldname": "purchase_invoice_settings_section",
"fieldtype": "Section Break",
"label": "Purchase Invoice Settings"
},
{
"fieldname": "column_break_lwxs",
"fieldtype": "Column Break"
},
{
"fieldname": "pricing_tab",
"fieldtype": "Tab Break",
"label": "Pricing"
},
{
"fieldname": "document_naming_tab",
"fieldtype": "Tab Break",
"label": "Document Naming"
},
{
"fieldname": "configure",
"fieldtype": "Button",
"hidden": 1,
"label": "Configure Series"
},
{
"fieldname": "transaction_naming_html",
"fieldtype": "HTML"
},
{
"fieldname": "naming_series_details",
"fieldtype": "Small Text",
"hidden": 1,
"is_virtual": 1,
"label": "Naming Series options"
}
],
"grid_page_length": 50,
"hide_toolbar": 0,
"icon": "fa fa-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-04-15 16:07:35.484787",
"modified": "2026-05-05 16:30:37.184607",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
@@ -313,7 +361,6 @@
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"role": "Purchase Manager",

View File

@@ -491,7 +491,6 @@ class PurchaseOrder(BuyingController):
self.update_status_updater_if_from_pp()
if self.has_drop_ship_item():
self.update_delivered_qty_in_sales_order()
self.set_received_qty_to_zero_for_drop_ship_items()
self.update_receiving_percentage()
@@ -623,9 +622,17 @@ class PurchaseOrder(BuyingController):
)
)
item.received_qty += d.get("qty_change")
qty_change = item.received_qty + d.get("qty_change")
item.db_set("received_qty", qty_change, update_modified=True)
self.add_comment(
"Label",
_("updated delivered quantity for item {0} to {1}").format(
frappe.bold(item.item_code), frappe.bold(qty_change)
),
)
self.update_receiving_percentage()
self.save()
self.set_status(update=True)
self.update_delivered_qty_in_sales_order()
def is_against_so(self):
return any(d.sales_order for d in self.items if d.sales_order)

View File

@@ -622,7 +622,6 @@
"width": "100px"
},
{
"allow_on_submit": 1,
"depends_on": "received_qty",
"fieldname": "received_qty",
"fieldtype": "Float",
@@ -952,7 +951,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-05-08 20:40:10.683023",
"modified": "2026-05-14 12:16:16.192936",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@@ -461,17 +461,7 @@ class BuyingController(SubcontractingController):
get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
)
net_rate = (
flt(
(item.base_net_amount / item.received_qty) * item.qty,
item.precision("base_net_amount"),
)
if item.received_qty
and frappe.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
else item.base_net_amount
)
net_rate = item.base_net_amount
if item.sales_incoming_rate: # for internal transfer
net_rate = item.qty * item.sales_incoming_rate

View File

@@ -269,14 +269,15 @@ class StockController(AccountsController):
)
is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items"))
if (
need_inventory_map = (self.get_stock_items() or self.get("packed_items")) and (
cint(erpnext.is_perpetual_inventory_enabled(self.company))
or provisional_accounting_for_non_stock_items
or is_asset_pr
):
)
inventory_account_map = frappe._dict()
if need_inventory_map:
inventory_account_map = self.get_inventory_account_map()
if need_inventory_map or provisional_accounting_for_non_stock_items or is_asset_pr:
if self.docstatus == 1:
if not gl_entries:
gl_entries = (

View File

@@ -169,10 +169,6 @@ class calculate_taxes_and_totals:
return
if not self.discount_amount_applied:
bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
do_not_round_fields = ["valuation_rate", "incoming_rate"]
for item in self.doc.items:
@@ -236,13 +232,7 @@ class calculate_taxes_and_totals:
elif not item.qty and self.doc.get("is_debit_note"):
item.amount = flt(item.rate, item.precision("amount"))
else:
qty = (
(item.qty + item.rejected_qty)
if bill_for_rejected_quantity_in_purchase_invoice
and self.doc.doctype == "Purchase Receipt"
else item.qty
)
item.amount = flt(item.rate * qty, item.precision("amount"))
item.amount = flt(item.rate * item.qty, item.precision("amount"))
item.net_amount = item.amount
@@ -402,16 +392,9 @@ class calculate_taxes_and_totals:
self.doc.total
) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
for item in self._items:
self.doc.total += item.amount
self.doc.total_qty += (
(item.qty + item.rejected_qty)
if bill_for_rejected_quantity_in_purchase_invoice and self.doc.doctype == "Purchase Receipt"
else item.qty
)
self.doc.total_qty += item.qty
self.doc.base_total += item.base_amount
self.doc.net_total += item.net_amount
self.doc.base_net_total += item.base_net_amount

File diff suppressed because it is too large Load Diff

View File

@@ -292,7 +292,7 @@ class MasterProductionSchedule(Document):
return item_wise_data
def add_mps_data(self, data):
data = frappe._dict(sorted(data.items(), key=lambda x: x[0][1]))
data = frappe._dict(sorted(data.items(), key=lambda x: x[0][1] or ""))
for key in data:
row = data[key]

View File

@@ -94,6 +94,7 @@ def create_items():
"is_stock_item": 1,
"standard_rate": 100,
"opening_stock": 100,
"valuation_rate": 100,
"last_purchase_rate": 100,
"item_defaults": [{"company": "_Test Company", "default_warehouse": "Stores - _TC"}],
}
@@ -103,6 +104,7 @@ def create_items():
"is_stock_item": 1,
"standard_rate": 200,
"opening_stock": 200,
"valuation_rate": 200,
"last_purchase_rate": 200,
"item_defaults": [{"company": "_Test Company", "default_warehouse": "Stores - _TC"}],
}

View File

@@ -481,3 +481,4 @@ erpnext.patches.v16_0.scr_inv_dimension
erpnext.patches.v16_0.packed_item_inv_dimen
erpnext.patches.v16_0.fix_titles
erpnext.patches.v16_0.set_not_applicable_on_german_item_tax_templates
erpnext.patches.v16_0.clear_procedures_from_receivable_report

View File

@@ -0,0 +1,12 @@
import frappe
def execute():
frappe.db.sql("drop function if exists ar_genkey")
frappe.db.sql("drop procedure if exists ar_init_tmp_table")
frappe.db.sql("drop procedure if exists ar_allocate_to_tmp_table")
if frappe.db.get_single_value("Accounts Settings", "receivable_payable_fetch_method") == "Raw SQL":
frappe.db.set_single_value(
"Accounts Settings", "receivable_payable_fetch_method", "UnBuffered Cursor"
)

View File

@@ -37,6 +37,6 @@ import "./utils/demo.js";
import "./financial_statements.js";
import "./sales_trends_filters.js";
import "./purchase_trends_filters.js";
import "./utils/naming_series_dialog.js";
import "./utils/naming_series.js";
// import { sum } from 'frappe/public/utils/util.js'

View File

@@ -207,6 +207,7 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
this.frm = opts.frm;
this.transactions = opts.transactions || [];
this.$wrapper = opts.frm.get_field(opts.fieldname).$wrapper;
this.theme_observer = null;
}
render() {
this.$wrapper.html(`
@@ -231,6 +232,21 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
const $rows = this.$wrapper.find(".naming-series-table-rows");
this.map_configure_button($rows);
this.get_row_data($rows);
if (this.theme_observer) {
this.theme_observer.disconnect();
}
const observer = new MutationObserver(() => {
const badge_class = this.get_current_badge_class();
this.$wrapper.find(".badge").removeClass("badge-light badge-dark").addClass(badge_class);
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});
}
map_configure_button($rows) {
@@ -258,9 +274,11 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
});
}
get_row_data($rows) {
this.transactions.forEach((t) => {
frappe.model.with_doctype(t.doctype, () => {
async get_row_data($rows) {
const rows = await Promise.all(
this.transactions.map(async (t) => {
await new Promise((resolve) => frappe.model.with_doctype(t.doctype, resolve));
const meta = frappe.get_meta(t.doctype);
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
const series = (naming_df?.options || "")
@@ -268,9 +286,12 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
.map((s) => s.trim())
.filter(Boolean);
$rows.append(this.make_row(t, series));
});
});
return this.make_row(t, series);
})
);
$rows.empty();
rows.forEach(($row) => $rows.append($row));
}
make_row(t, series) {
@@ -296,13 +317,20 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
`);
}
get_current_badge_class() {
return document.documentElement.getAttribute("data-theme") === "dark" ? "badge-dark" : "badge-light";
}
series_list_background(series_list) {
if (!series_list.length) {
return `<span class="text-muted">${__("Not configured")}</span>`;
}
const badge_class = this.get_current_badge_class();
return series_list
.map(
(s) => `<span class="badge badge-light"
(s) => `<span class="badge ${badge_class}"
style="margin: 2px; font-family: monospace; font-weight: normal;">
${frappe.utils.escape_html(s)}
</span>`
@@ -310,3 +338,80 @@ erpnext.NamingSeriesTable = class NamingSeriesTable {
.join("");
}
};
/**
* @param {Object} frm - Frappe form instance.
*/
erpnext.NamingSeriesController = class NamingSeriesController {
constructor(frm) {
this.frm = frm;
}
/**
* Renders the naming series table in the given field.
*
* @param {string} fieldname - Fieldname where the table should be rendered.
* @param {Array<{doctype: string, label: string}>} [transactions=[]] - Transactions to display.
* @returns {void}
*/
render_table(fieldname, transactions = []) {
this.frm._naming_series_table = new erpnext.NamingSeriesTable({
frm: this.frm,
fieldname: fieldname,
transactions: transactions,
});
this.frm._naming_series_table.render();
}
/**
* Loads naming series from the given master doctype into a field.
*
* @param {string} doctype - Master doctype name.
* @param {string} field - Fieldname where series should be shown.
* @returns {void}
*/
load_master_series(doctype, field) {
frappe.model.with_doctype(doctype, () => {
const meta = frappe.get_meta(doctype);
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
const options = naming_df?.options || "";
const series_list = options
.split("\n")
.map((s) => s.trim())
.filter(Boolean);
this.frm.doc[field] = series_list.length
? series_list.join("\n")
: __("No naming series defined");
this.frm.refresh_field(field);
});
}
/**
* Opens the naming series dialog for a doctype.
*
* @param {string} doctype - Transaction doctype.
* @param {Function} [on_update] - Called after series are updated.
* @returns {void}
*/
show_naming_series_dialog(doctype, on_update) {
if (!this.frm._naming_dialogs) this.frm._naming_dialogs = {};
if (!this.frm._naming_dialogs[doctype]) {
this.frm._naming_dialogs[doctype] = new erpnext.NamingSeriesDialog({
doctype: doctype,
title: __("{0} Naming Series", [__(doctype)]),
on_update: ({ naming_series_options }) => {
const series = naming_series_options.split("\n").filter(Boolean);
this.frm
.get_field(this.opts.table_field)
.$wrapper.find(`.series-cell-${frappe.scrub(doctype)}`)
.html(this.frm._naming_series_table?.series_list_background(series));
on_update?.({ doctype, naming_series_options });
},
});
}
this.frm._naming_dialogs[doctype].show();
}
};

View File

@@ -6,6 +6,7 @@ import erpnext
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.regional.report.uae_vat_201.uae_vat_201 import (
execute,
get_exempt_total,
get_standard_rated_expenses_tax,
get_standard_rated_expenses_total,
@@ -32,6 +33,13 @@ class TestUaeVat201(ERPNextTestSuite):
make_item("_Test UAE VAT Zero Rated Item", properties={"is_zero_rated": 1, "is_exempt": 0})
make_item("_Test UAE VAT Exempt Item", properties={"is_zero_rated": 0, "is_exempt": 1})
def test_validate_company_region(self):
self.assertRaises(
frappe.exceptions.ValidationError,
execute,
{"company": "_Test Company"},
)
def test_uae_vat_201_report(self):
make_sales_invoices()
create_purchase_invoices()

View File

@@ -10,6 +10,13 @@ frappe.query_reports["UAE VAT 201"] = {
options: "Company",
reqd: 1,
default: frappe.defaults.get_user_default("Company"),
get_query: function () {
return {
filters: {
country: "United Arab Emirates",
},
};
},
},
{
fieldname: "from_date",

View File

@@ -5,13 +5,25 @@
import frappe
from frappe import _
from erpnext import get_region
def execute(filters=None):
validate_company_region(filters)
columns = get_columns()
data, emirates, amounts_by_emirate = get_data(filters)
return columns, data
def validate_company_region(filters):
if filters.get("company") and get_region(filters.get("company")) != "United Arab Emirates":
frappe.throw(
_(
"The company {0} is not in United Arab Emirates. UAE VAT 201 report is only available for companies in United Arab Emirates."
).format(frappe.bold(filters.get("company")))
)
def get_columns():
"""Creates a list of dictionaries that are used to generate column headers of the data table."""
return [

View File

@@ -684,18 +684,12 @@ class SalesOrder(SellingController):
for item in self.items:
if item.delivered_by_supplier:
item_delivered_qty = frappe.db.sql(
"""select sum(qty)
from `tabPurchase Order Item` poi, `tabPurchase Order` po
where poi.sales_order_item = %s
and poi.item_code = %s
and poi.parent = po.name
and po.docstatus = 1
and po.status = 'Delivered'""",
(item.name, item.item_code),
)
item_delivered_qty = item_delivered_qty[0][0] if item_delivered_qty else 0
item_delivered_qty = frappe.get_all(
"Purchase Order Item",
{"sales_order_item": item.name, "docstatus": 1},
[{"SUM": "received_qty", "AS": "received_qty"}],
pluck="received_qty",
)[0]
item.db_set("delivered_qty", flt(item_delivered_qty), update_modified=False)
delivered_qty += min(item.delivered_qty, item.qty)

View File

@@ -1224,9 +1224,14 @@ class TestSalesOrder(ERPNextTestSuite):
self.assertEqual(abs(flt(reserved_qty)), 0)
# test per_delivered status
update_status("Delivered", po.name)
self.assertEqual(po.status, "To Receive and Bill")
self.assertEqual(so.status, "To Deliver and Bill")
po.update_dropship_received_qty([{"name": po.items[0].name, "qty_change": 2}])
self.assertEqual(flt(frappe.db.get_value("Sales Order", so.name, "per_delivered"), 2), 100.00)
po.load_from_db()
so.reload()
self.assertEqual(po.status, "To Bill")
self.assertEqual(so.status, "To Bill")
# test after closing so
so.db_set("status", "Closed")

View File

@@ -3,27 +3,39 @@
frappe.ui.form.on("Selling Settings", {
refresh(frm) {
if (!frm.naming_controller) frm.naming_controller = new erpnext.NamingSeriesController(frm);
const display = frm.doc.cust_master_name === "Naming Series";
frm.set_df_property("naming_series_details", "hidden", !display);
frm.set_df_property("configure", "hidden", !display);
if (display) {
find_naming_series("Customer", "naming_series_details", frm);
frm.naming_controller.load_master_series("Customer", "naming_series_details");
}
load_default_naming_series(frm);
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
},
cust_master_name(frm) {
const display = frm.doc.cust_master_name === "Naming Series";
frm.set_df_property("naming_series_details", "hidden", !display);
frm.set_df_property("configure", "hidden", !display);
if (display) {
find_naming_series("Customer", "naming_series_details", frm);
frm.naming_controller.load_master_series("Customer", "naming_series_details");
} else {
frm.set_value("naming_series_details", "");
frm.doc.naming_series_details = "";
frm.refresh_field("naming_series_details");
}
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
},
configure(frm) {
show_naming_series_dialog("Customer", frm);
frm.naming_controller.show_naming_series_dialog("Customer", ({ naming_series_options }) => {
frm.doc.naming_series_details = naming_series_options;
frm.refresh_field("naming_series_details");
});
},
after_save(frm) {
@@ -31,51 +43,18 @@ frappe.ui.form.on("Selling Settings", {
},
});
function show_naming_series_dialog(doctype, frm) {
if (!frm._naming_series_dialog) {
frm._naming_series_dialog = new erpnext.NamingSeriesDialog({
doctype: doctype,
title: __("Naming Series for {0}", [__(doctype)]),
on_update: ({ naming_series_options }) => {
frm.set_value("naming_series_details", naming_series_options);
},
});
}
frm._naming_series_dialog.show();
}
function find_naming_series(doctype, field, frm) {
frappe.model.with_doctype(doctype, () => {
const meta = frappe.get_meta(doctype);
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
const options = naming_df?.options || "";
const series_list = options
.split("\n")
.map((s) => s.trim())
.filter(Boolean);
frm.doc[field] = series_list.length ? series_list.join("\n") : __("No naming series defined");
frm.refresh_field(field);
});
}
function load_default_naming_series(frm) {
let transactions = [
function get_transactions(frm) {
const transactions = [
{ label: __("Customer"), doctype: "Customer" },
{ label: __("Quotation"), doctype: "Quotation" },
{ label: __("Sales Order"), doctype: "Sales Order" },
{ label: __("Sales Invoice"), doctype: "Sales Invoice" },
{ label: __("Delivery Note"), doctype: "Delivery Note" },
{ label: __("Payment Entry"), doctype: "Payment Entry" },
{ label: __("POS Invoice"), doctype: "POS Invoice" },
];
if (frm.doc.cust_master_name !== "Naming Series") {
transactions = transactions.filter((t) => t.doctype !== "Customer");
return transactions.filter((t) => t.doctype !== "Customer");
}
new erpnext.NamingSeriesTable({
frm: frm,
fieldname: "transaction_naming_html",
transactions: transactions,
}).render();
return transactions;
}

View File

@@ -1085,7 +1085,6 @@ def create_transaction_deletion_request(company):
tdr.reload()
tdr.submit()
tdr.start_deletion_tasks()
frappe.msgprint(
_("Transaction Deletion Document {0} has been triggered for company {1}").format(

View File

@@ -396,7 +396,6 @@ def create_and_submit_transaction_deletion_doc(company):
tdr.process_in_single_transaction = True
tdr.submit()
tdr.start_deletion_tasks()
return tdr

View File

@@ -736,10 +736,11 @@ class TransactionDeletionRecord(Document):
self.enqueue_task(task="Clear Notifications")
return
company_obj = frappe.get_doc("Company", self.company)
company_obj.total_monthly_sales = 0
company_obj.sales_monthly_history = None
company_obj.save()
frappe.db.set_value(
"Company",
self.company,
{"total_monthly_sales": 0, "sales_monthly_history": None},
)
self.db_set("reset_company_default_values_status", "Completed")
self.enqueue_task(task="Clear Notifications")

View File

@@ -48,9 +48,18 @@ class DeprecatedSerialNoValuation:
if not posting_datetime and self.sle.posting_date:
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
do_not_fetch_rate = frappe.db.get_single_value(
"Stock Reposting Settings", "do_not_fetch_incoming_rate_from_serial_no"
)
for serial_no in serial_nos:
sn_details = frappe.db.get_value("Serial No", serial_no, ["purchase_rate", "company"], as_dict=1)
if sn_details and sn_details.purchase_rate and sn_details.company == self.sle.company:
if (
sn_details
and sn_details.purchase_rate
and sn_details.company == self.sle.company
and (not frappe.flags.through_repost_item_valuation or not do_not_fetch_rate)
):
self.serial_no_incoming_rate[serial_no] += flt(sn_details.purchase_rate)
incoming_values += self.serial_no_incoming_rate[serial_no]
continue

View File

@@ -73,6 +73,7 @@ frappe.ui.form.on("Item", {
},
};
},
onload: function (frm) {
erpnext.item.setup_queries(frm);
if (frm.doc.variant_of) {
@@ -127,6 +128,21 @@ frappe.ui.form.on("Item", {
refresh: function (frm) {
frm.trigger("toggle_has_serial_batch_fields");
if (frappe.defaults.get_default("item_naming_by") != "Naming Series" || frm.doc.variant_of) {
frm.toggle_display("naming_series", false);
} else {
erpnext.toggle_naming_series();
}
frm.toggle_display(["standard_rate"], frappe.model.can_create("Item Price"));
if (frm.is_new()) {
frm.toggle_display("disabled", false);
return;
}
frm.toggle_display("disabled", true);
if (frm.doc.is_stock_item) {
frm.add_custom_button(
__("Stock Balance"),
@@ -229,8 +245,6 @@ frappe.ui.form.on("Item", {
__("Create")
);
}
// frm.page.set_inner_btn_group_as_primary(__('Create'));
}
if (frm.doc.variant_of) {
frm.set_intro(
@@ -241,12 +255,6 @@ frappe.ui.form.on("Item", {
);
}
if (frappe.defaults.get_default("item_naming_by") != "Naming Series" || frm.doc.variant_of) {
frm.toggle_display("naming_series", false);
} else {
erpnext.toggle_naming_series();
}
erpnext.item.edit_prices_button(frm);
erpnext.item.toggle_attributes(frm);
@@ -286,8 +294,6 @@ frappe.ui.form.on("Item", {
},
};
});
frm.toggle_display(["standard_rate"], frappe.model.can_create("Item Price"));
},
validate: function (frm) {
@@ -661,10 +667,10 @@ $.extend(erpnext.item, {
make_dashboard: function (frm) {
if (frm.doc.__islocal) return;
// Show Stock Levels only if is_stock_item
if (frm.doc.is_stock_item) {
frappe.require("item-dashboard.bundle.js", function () {
const section = frm.dashboard.add_section("", __("Stock Levels"));
const section = frm.fields_dict["stock_levels_html"].$wrapper;
erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({
parent: section,
item_code: frm.doc.name,

View File

@@ -18,30 +18,57 @@
"stock_uom",
"column_break0",
"disabled",
"allow_alternative_item",
"is_stock_item",
"has_variants",
"is_fixed_asset",
"auto_create_assets",
"is_grouped_asset",
"asset_category",
"asset_naming_series",
"section_break_zlmj",
"is_sales_item",
"allow_alternative_item",
"has_variants",
"column_break_kpmi",
"is_purchase_item",
"is_customer_provided_item",
"customer",
"section_break_gjns",
"opening_stock",
"column_break_ixrh",
"standard_rate",
"column_break_ixrh",
"opening_stock",
"section_break_znra",
"over_delivery_receipt_allowance",
"column_break_wugd",
"over_billing_allowance",
"image",
"section_break_11",
"description",
"brand",
"description",
"accounting",
"section_break_wfkx",
"item_defaults",
"deferred_accounting_section",
"enable_deferred_expense",
"no_of_months_exp",
"column_break_9s9o",
"enable_deferred_revenue",
"no_of_months",
"uom_tab",
"unit_of_measure_conversion",
"uom_conversion_details_column",
"uom_help_html",
"uoms",
"dashboard_tab",
"item_tax_section_break",
"section_break_oilf",
"column_break_aytr",
"taxes",
"section_break_fxqz",
"purchase_tax_withholding_category",
"column_break_ltlb",
"sales_tax_withholding_category",
"inventory_section",
"stock_levels_section",
"stock_levels_html",
"inventory_valuation_section",
"valuation_method",
"column_break_cqdk",
@@ -69,32 +96,20 @@
"column_break_37",
"has_serial_no",
"serial_no_series",
"defaults_tab",
"item_defaults",
"variants_section",
"variant_of",
"variant_based_on",
"attributes",
"accounting",
"deferred_accounting_section",
"enable_deferred_expense",
"no_of_months_exp",
"column_break_9s9o",
"enable_deferred_revenue",
"no_of_months",
"purchasing_tab",
"purchase_uom",
"min_order_qty",
"safety_stock",
"is_purchase_item",
"purchase_details_cb",
"lead_time_days",
"last_purchase_rate",
"is_customer_provided_item",
"customer",
"supplier_details",
"delivered_by_supplier",
"column_break2",
"section_break_ylma",
"supplier_items",
"foreign_trade_details",
"country_of_origin",
@@ -103,24 +118,10 @@
"sales_details",
"sales_uom",
"grant_commission",
"is_sales_item",
"column_break3",
"max_discount",
"customer_details",
"customer_items",
"item_tax_section_break",
"section_break_oilf",
"column_break_aytr",
"taxes",
"section_break_fxqz",
"purchase_tax_withholding_category",
"column_break_ltlb",
"sales_tax_withholding_category",
"quality_tab",
"inspection_required_before_purchase",
"inspection_required_before_delivery",
"column_break_pxjh",
"quality_inspection_template",
"manufacturing",
"include_item_in_manufacturing",
"is_sub_contracted_item",
@@ -129,10 +130,16 @@
"production_capacity",
"total_projected_qty",
"section_break_xili",
"customer_code",
"column_break_vipt",
"default_manufacturer_part_no",
"default_item_manufacturer"
"default_item_manufacturer",
"column_break_vipt",
"customer_code",
"quality_tab",
"inspection_required_before_purchase",
"inspection_required_before_delivery",
"column_break_pxjh",
"quality_inspection_template",
"dashboard_tab"
],
"fields": [
{
@@ -208,32 +215,38 @@
},
{
"default": "0",
"description": "Disabled items cannot be selected in any transaction.",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled",
"search_index": 1
"search_index": 1,
"show_description_on_click": 1
},
{
"default": "0",
"description": "Allow substituting this item with an alternative from the Item Alternative list when stock is unavailable.",
"fieldname": "allow_alternative_item",
"fieldtype": "Check",
"label": "Allow Alternative Item"
"label": "Allow Alternative Item",
"show_description_on_click": 1
},
{
"allow_in_quick_entry": 1,
"bold": 1,
"default": "1",
"depends_on": "eval:!doc.is_fixed_asset",
"description": "ERPNext will make a stock ledger entry for each transaction of this item. Keep unchecked for non-stock or service items.",
"fieldname": "is_stock_item",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Maintain Stock",
"oldfieldname": "is_stock_item",
"oldfieldtype": "Select"
"oldfieldtype": "Select",
"read_only_depends_on": "eval:doc.is_fixed_asset"
},
{
"default": "1",
"depends_on": "eval:!doc.is_fixed_asset",
"description": "Enable for raw material items used in BOM. Uncheck for additional services like 'washing' used in manufacturing.",
"fieldname": "include_item_in_manufacturing",
"fieldtype": "Check",
"label": "Include Item In Manufacturing"
@@ -241,6 +254,7 @@
{
"bold": 1,
"depends_on": "eval:(doc.__islocal&&doc.is_stock_item && !doc.has_serial_no && !doc.has_batch_no)",
"description": "Used to create an opening Stock Entry with the Valuation Rate when the item is saved",
"fieldname": "opening_stock",
"fieldtype": "Float",
"label": "Opening Stock"
@@ -253,7 +267,8 @@
},
{
"bold": 1,
"depends_on": "eval:doc.__islocal",
"depends_on": "eval:doc.__islocal && doc.is_sales_item",
"description": "Creates an Item Price automatically when the item is saved",
"fieldname": "standard_rate",
"fieldtype": "Currency",
"label": "Standard Selling Rate"
@@ -261,9 +276,11 @@
{
"allow_in_quick_entry": 1,
"default": "0",
"description": "Enable if this item is a company asset like machinery or furniture.",
"fieldname": "is_fixed_asset",
"fieldtype": "Check",
"label": "Is Fixed Asset"
"label": "Is Fixed Asset",
"read_only_depends_on": "eval:doc.is_stock_item"
},
{
"allow_in_quick_entry": 1,
@@ -413,16 +430,12 @@
"options": "Item Reorder"
},
{
"collapsible": 1,
"fieldname": "unit_of_measure_conversion",
"fieldtype": "Section Break",
"label": "Units of Measure"
"fieldtype": "Section Break"
},
{
"description": "Will also apply for variants",
"fieldname": "uoms",
"fieldtype": "Table",
"label": "UOMs",
"oldfieldname": "uom_conversion_details",
"oldfieldtype": "Table",
"options": "UOM Conversion Detail"
@@ -438,15 +451,18 @@
{
"default": "0",
"depends_on": "eval:doc.is_stock_item",
"description": "Track this item in batches. Cannot be changed after a stock transaction exists.",
"fieldname": "has_batch_no",
"fieldtype": "Check",
"label": "Has Batch No",
"oldfieldname": "has_batch_no",
"oldfieldtype": "Select"
"oldfieldtype": "Select",
"show_description_on_click": 1
},
{
"default": "0",
"depends_on": "has_batch_no",
"description": "Batch number will be auto-created in format AAAA.00001 if not specified in transactions. Leave blank to always enter batch numbers manually.",
"fieldname": "create_new_batch",
"fieldtype": "Check",
"label": "Automatically Create New Batch"
@@ -463,6 +479,7 @@
{
"default": "0",
"depends_on": "has_batch_no",
"description": "Batch number will be created based on expiry date. Expiry dates can be set in the Batch master.",
"fieldname": "has_expiry_date",
"fieldtype": "Check",
"label": "Has Expiry Date"
@@ -491,11 +508,13 @@
{
"default": "0",
"depends_on": "eval:doc.is_stock_item",
"description": "Track each unit with a unique serial number for warranty and return tracking. Cannot be changed after a stock transaction exists.",
"fieldname": "has_serial_no",
"fieldtype": "Check",
"label": "Has Serial No",
"oldfieldname": "has_serial_no",
"oldfieldtype": "Select"
"oldfieldtype": "Select",
"show_description_on_click": 1
},
{
"depends_on": "has_serial_no",
@@ -520,7 +539,8 @@
"fieldname": "has_variants",
"fieldtype": "Check",
"in_standard_filter": 1,
"label": "Has Variants"
"label": "Has Variants",
"show_description_on_click": 1
},
{
"default": "Item Attribute",
@@ -547,11 +567,15 @@
},
{
"default": "1",
"description": "Allow this item to be used in purchase transactions.",
"fieldname": "is_purchase_item",
"fieldtype": "Check",
"label": "Allow Purchase"
"label": "Allow Purchase",
"read_only_depends_on": "eval: doc.is_customer_provided_item",
"show_description_on_click": 1
},
{
"depends_on": "eval: doc.is_purchase_item",
"fieldname": "purchase_uom",
"fieldtype": "Link",
"label": "Default Purchase Unit of Measure",
@@ -560,7 +584,7 @@
{
"default": "0.00",
"depends_on": "is_stock_item",
"description": "Minimum quantity should be as per Stock UOM",
"description": "Minimum quantity should be as per Stock UOM\n\n",
"fieldname": "min_order_qty",
"fieldtype": "Float",
"label": "Minimum Order Qty",
@@ -569,6 +593,7 @@
"oldfieldtype": "Currency"
},
{
"description": "Minimum stock level to maintain as a buffer. Used to calculate recommended reorder level: Reorder Level = Safety Stock + (Average Daily Consumption \u00d7 Lead Time).",
"fieldname": "safety_stock",
"fieldtype": "Float",
"label": "Safety Stock",
@@ -588,6 +613,7 @@
"oldfieldtype": "Int"
},
{
"description": "The rate at which this item was last purchased via a Purchase Invoice. Auto-updated by the system.",
"fieldname": "last_purchase_rate",
"fieldtype": "Float",
"label": "Last Purchase Rate",
@@ -599,9 +625,12 @@
},
{
"default": "0",
"description": "Enable if this item is provided by a customer and received via Stock Entry.",
"fieldname": "is_customer_provided_item",
"fieldtype": "Check",
"label": "Is Customer Provided Item"
"label": "Is Customer Provided Item",
"read_only_depends_on": "eval: doc.is_purchase_item",
"show_description_on_click": 1
},
{
"depends_on": "eval:doc.is_customer_provided_item",
@@ -611,29 +640,24 @@
"options": "Customer"
},
{
"collapsible": 1,
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "supplier_details",
"fieldtype": "Section Break",
"hide_border": 1,
"label": "Supplier Details"
},
{
"default": "0",
"description": "Enable for drop shipping - supplier delivers directly to the customer without passing through your warehouse.",
"fieldname": "delivered_by_supplier",
"fieldtype": "Check",
"label": "Delivered by Supplier (Drop Ship)",
"print_hide": 1
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "supplier_items",
"fieldtype": "Table",
"label": "Supplier Items",
"label": "Item Supplier",
"options": "Item Supplier"
},
{
@@ -660,6 +684,7 @@
},
{
"collapsible": 1,
"depends_on": "eval: doc.is_sales_item",
"fieldname": "sales_details",
"fieldtype": "Tab Break",
"label": "Sales",
@@ -667,6 +692,7 @@
"options": "fa fa-tag"
},
{
"depends_on": "eval: doc.is_sales_item",
"fieldname": "sales_uom",
"fieldtype": "Link",
"label": "Default Sales Unit of Measure",
@@ -674,9 +700,11 @@
},
{
"default": "1",
"description": "Allow this item to be used in sales transactions.",
"fieldname": "is_sales_item",
"fieldtype": "Check",
"label": "Allow Sales"
"label": "Allow Sales",
"show_description_on_click": 1
},
{
"fieldname": "column_break3",
@@ -685,6 +713,7 @@
"width": "50%"
},
{
"description": "Maximum discount % allowed when selling this item. Eg: if set to 20%, a discount greater than 20% cannot be applied in sales transactions.",
"fieldname": "max_discount",
"fieldtype": "Float",
"label": "Max Discount (%)",
@@ -693,6 +722,7 @@
},
{
"default": "0",
"description": "Expense for this item will be recognized over a period of months. Eg: prepaid insurance or annual software license",
"fieldname": "enable_deferred_revenue",
"fieldtype": "Check",
"label": "Enable Deferred Revenue"
@@ -705,6 +735,7 @@
},
{
"default": "0",
"description": "Income from this item will be recognized over a period of months instead of all at once. Eg: annual subscription paid upfront.",
"fieldname": "enable_deferred_expense",
"fieldtype": "Check",
"label": "Enable Deferred Expense"
@@ -716,7 +747,6 @@
"label": "No of Months (Expense)"
},
{
"collapsible": 1,
"depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "customer_details",
"fieldtype": "Section Break",
@@ -748,17 +778,21 @@
},
{
"default": "0",
"description": "A quality inspection must be completed before generating a Purchase Receipt for this item.",
"fieldname": "inspection_required_before_purchase",
"fieldtype": "Check",
"label": "Inspection Required before Purchase",
"oldfieldname": "inspection_required",
"oldfieldtype": "Select"
"oldfieldtype": "Select",
"show_description_on_click": 1
},
{
"default": "0",
"description": "A quality inspection must be completed before generating a Delivery Note for this item.",
"fieldname": "inspection_required_before_delivery",
"fieldtype": "Check",
"label": "Inspection Required before Delivery"
"label": "Inspection Required before Delivery",
"show_description_on_click": 1
},
{
"fieldname": "quality_inspection_template",
@@ -789,6 +823,7 @@
},
{
"default": "0",
"description": "Enable if a vendor manufactures this item for you. You can choose to provide them raw materials using the default BOM.",
"fieldname": "is_sub_contracted_item",
"fieldtype": "Check",
"label": "Is Subcontracted Item",
@@ -817,6 +852,7 @@
},
{
"depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset",
"description": "Percentage by which over-delivery or over-receipt is allowed against a Sales/Purchase Order for this item. If not set, value from Stock Settings will be used.",
"fieldname": "over_delivery_receipt_allowance",
"fieldtype": "Float",
"label": "Over Delivery/Receipt Allowance (%)",
@@ -825,6 +861,7 @@
},
{
"depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset",
"description": "Percentage by which over-billing is allowed against a Sales/Purchase Order for this item. If not set, value from Accounts Settings will be used.",
"fieldname": "over_billing_allowance",
"fieldtype": "Float",
"label": "Over Billing Allowance (%)"
@@ -834,7 +871,7 @@
"depends_on": "is_fixed_asset",
"fieldname": "auto_create_assets",
"fieldtype": "Check",
"label": "Auto Create Assets on Purchase"
"label": "Auto create assets on purchase"
},
{
"fieldname": "default_item_manufacturer",
@@ -851,19 +888,24 @@
},
{
"default": "1",
"description": "If enabled, sales from this item will be included in Sales Person and Sales Partner commission calculations",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission"
"label": "Grant Commission",
"show_description_on_click": 1
},
{
"default": "0",
"depends_on": "auto_create_assets",
"depends_on": "eval: doc.is_fixed_asset && doc.auto_create_assets",
"description": "Creates a single grouped asset instead of individual assets when purchased in bulk.",
"fieldname": "is_grouped_asset",
"fieldtype": "Check",
"label": "Create Grouped Asset"
"label": "Create Grouped Asset",
"show_description_on_click": 1
},
{
"default": "0",
"description": "Allow stock to go below zero for this item, even if negative stock is disabled in Stock Settings.",
"fieldname": "allow_negative_stock",
"fieldtype": "Check",
"label": "Allow Negative Stock"
@@ -874,6 +916,7 @@
"label": "Inventory Settings"
},
{
"depends_on": "eval: doc.is_purchase_item",
"fieldname": "purchasing_tab",
"fieldtype": "Tab Break",
"label": "Purchasing"
@@ -893,7 +936,7 @@
{
"fieldname": "dashboard_tab",
"fieldtype": "Tab Break",
"label": "Dashboard",
"label": "Connections",
"show_dashboard": 1
},
{
@@ -916,11 +959,6 @@
"fieldtype": "Int",
"label": "Production Capacity"
},
{
"fieldname": "defaults_tab",
"fieldtype": "Tab Break",
"label": "Defaults"
},
{
"fieldname": "section_break_znra",
"fieldtype": "Section Break"
@@ -988,6 +1026,50 @@
{
"fieldname": "column_break_vipt",
"fieldtype": "Column Break"
},
{
"fieldname": "stock_levels_html",
"fieldtype": "HTML",
"label": "Stock Levels HTML",
"options": "<div id=\"stock-levels-placeholder\"></div>"
},
{
"fieldname": "section_break_wfkx",
"fieldtype": "Section Break"
},
{
"fieldname": "section_break_ylma",
"fieldtype": "Section Break"
},
{
"fieldname": "uom_tab",
"fieldtype": "Tab Break",
"label": "UOM"
},
{
"depends_on": "eval: !doc.__islocal && doc.is_stock_item",
"fieldname": "stock_levels_section",
"fieldtype": "Section Break",
"label": "Stock Levels"
},
{
"fieldname": "uom_conversion_details_column",
"fieldtype": "Column Break",
"label": "UOM Conversion Details"
},
{
"fieldname": "uom_help_html",
"fieldtype": "HTML",
"options": "<div style=\"margin-bottom:10px; color: var(--gray-500) !important;\">Define alternate units for this item. Eg: 1 Box = 12 Nos, set conversion factor as 12. (Will also apply for variants) <a href=\"https://docs.frappe.io/erpnext/uom\" target=\"_blank\">Learn more &#8594;</a></div>"
},
{
"fieldname": "section_break_zlmj",
"fieldtype": "Section Break",
"label": "Item Attributes"
},
{
"fieldname": "column_break_kpmi",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-tag",
@@ -995,7 +1077,7 @@
"image_field": "image",
"links": [],
"make_attachments_public": 1,
"modified": "2026-04-14 13:37:00.183583",
"modified": "2026-04-28 17:31:47.613279",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",

View File

@@ -182,6 +182,14 @@ class Item(Document):
for default in self.item_defaults or [frappe._dict()]:
self.add_price(default.default_price_list)
frappe.msgprint(
_("Item Price created at rate {0}").format(
frappe.format(self.standard_rate, {"fieldtype": "Currency"})
),
indicator="green",
alert=True,
)
if self.opening_stock:
self.set_opening_stock()
@@ -285,7 +293,7 @@ class Item(Document):
if not self.is_stock_item or self.has_serial_no or self.has_batch_no:
return
if not self.valuation_rate and not self.standard_rate and not self.is_customer_provided_item:
if self.valuation_rate is None and not self.is_customer_provided_item:
frappe.throw(_("Valuation Rate is mandatory if Opening Stock entered"))
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -310,14 +318,38 @@ class Item(Document):
item_code=self.name,
target=default_warehouse,
qty=self.opening_stock,
rate=self.valuation_rate or self.standard_rate,
rate=self.valuation_rate,
company=default.company,
posting_date=getdate(),
posting_time=nowtime(),
do_not_save=True,
)
if self.valuation_rate == 0:
for item in stock_entry.items:
item.allow_zero_valuation_rate = 1
stock_entry.insert()
stock_entry.submit()
stock_entry.load_from_db()
stock_entry.add_comment("Comment", _("Opening Stock"))
stock_entry_link = frappe.utils.get_link_to_form("Stock Entry", stock_entry.name)
if self.valuation_rate == 0:
frappe.msgprint(
_("Opening Stock entry created with zero valuation rate: {0}").format(
stock_entry_link
),
indicator="orange",
alert=True,
)
else:
frappe.msgprint(
_("Opening Stock entry created: {0}").format(stock_entry_link),
indicator="green",
alert=True,
)
def validate_fixed_asset(self):
if self.is_fixed_asset:
if self.is_stock_item:

View File

@@ -36,6 +36,7 @@
"options": "Customer Group"
},
{
"description": "Enter the Item Code that this customer uses at their end. This will be shown in Sales Orders for the customer's reference.",
"fieldname": "ref_code",
"fieldtype": "Data",
"in_filter": 1,
@@ -52,13 +53,14 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:09:54.332166",
"modified": "2026-04-23 02:09:39.602010",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Customer Detail",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -55,11 +55,13 @@
"fieldtype": "Column Break"
},
{
"description": "Default price list for buying or selling this item",
"fieldname": "default_price_list",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Default Price List",
"options": "Price List"
"options": "Price List",
"show_description_on_click": 1
},
{
"fieldname": "purchase_defaults",
@@ -67,12 +69,15 @@
"label": "Purchase Defaults"
},
{
"description": "Cost center used for tracking purchase expenses for this item",
"fieldname": "buying_cost_center",
"fieldtype": "Link",
"label": "Default Buying Cost Center",
"options": "Cost Center"
"options": "Cost Center",
"show_description_on_click": 1
},
{
"description": "This supplier will be auto-selected in new purchase transactions",
"fieldname": "default_supplier",
"fieldtype": "Link",
"label": "Default Supplier",
@@ -83,10 +88,12 @@
"fieldtype": "Column Break"
},
{
"description": "Account where the cost of this item will be debited on purchase",
"fieldname": "expense_account",
"fieldtype": "Link",
"label": "Default Expense Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"fieldname": "selling_defaults",
@@ -94,20 +101,24 @@
"label": "Sales Defaults"
},
{
"description": "Cost center used for tracking sales revenue for this item",
"fieldname": "selling_cost_center",
"fieldtype": "Link",
"label": "Default Selling Cost Center",
"options": "Cost Center"
"options": "Cost Center",
"show_description_on_click": 1
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"description": "Account where revenue from selling this item will be credited",
"fieldname": "income_account",
"fieldtype": "Link",
"label": "Default Income Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"fieldname": "default_discount_account",
@@ -116,6 +127,7 @@
"options": "Account"
},
{
"description": "Provisional liability account used for service items before invoice is received",
"fieldname": "default_provisional_account",
"fieldtype": "Link",
"label": "Default Provisional Account (Service)",
@@ -128,17 +140,21 @@
},
{
"depends_on": "eval: parent.enable_deferred_expense",
"description": "When you pay for something upfront (like annual insurance), the cost is held here and recognized gradually over time",
"fieldname": "deferred_expense_account",
"fieldtype": "Link",
"label": "Deferred Expense Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"depends_on": "eval: parent.enable_deferred_revenue",
"description": "Revenue received in advance (e.g. annual subscription) is held here and recognized gradually over time",
"fieldname": "deferred_revenue_account",
"fieldtype": "Link",
"label": "Deferred Revenue Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"fieldname": "column_break_kwad",
@@ -154,28 +170,35 @@
"label": "Cost of Goods Sold"
},
{
"description": "Account where cost of goods sold will be posted when this item is sold",
"fieldname": "default_cogs_account",
"fieldtype": "Link",
"label": "Default COGS Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"description": "Account to record additional purchase expenses like freight or customs for this item",
"fieldname": "purchase_expense_account",
"fieldtype": "Link",
"label": "Purchase Expense Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"description": "Used to balance the books when recording extra purchase costs like freight or customs",
"fieldname": "purchase_expense_contra_account",
"fieldtype": "Link",
"label": "Purchase Expense Contra Account",
"options": "Account"
},
{
"description": "Stock account where inventory value for this item will be tracked",
"fieldname": "default_inventory_account",
"fieldtype": "Link",
"label": "Default Inventory Account",
"options": "Account"
"options": "Account",
"show_description_on_click": 1
},
{
"fetch_from": "default_inventory_account.account_currency",
@@ -188,7 +211,7 @@
],
"istable": 1,
"links": [],
"modified": "2025-10-21 10:50:46.144721",
"modified": "2026-04-27 01:49:01.396845",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Default",

View File

@@ -6,6 +6,7 @@
"engine": "InnoDB",
"field_order": [
"supplier",
"column_break_vcuv",
"supplier_part_no"
],
"fields": [
@@ -25,19 +26,24 @@
"label": "Supplier Part Number",
"print_width": "200px",
"width": "200px"
},
{
"fieldname": "column_break_vcuv",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:09:55.339052",
"modified": "2026-04-22 02:08:38.777228",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Supplier",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -561,14 +561,7 @@ class PurchaseReceipt(BuyingController):
else flt(item.net_amount, item.precision("net_amount"))
)
outgoing_amount = (
flt((item.base_net_amount / item.received_qty) * item.qty, item.precision("base_net_amount"))
if item.received_qty
and frappe.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
else item.base_net_amount
)
outgoing_amount = item.base_net_amount
if self.is_internal_transfer() and item.valuation_rate:
outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse))
credit_amount = outgoing_amount

View File

@@ -4610,7 +4610,7 @@ class TestPurchaseReceipt(ERPNextTestSuite):
self.assertEqual(srbnb_cost, 1500)
def test_valuation_rate_for_rejected_materials_without_accepted_materials(self):
def test_valuation_rate_for_rejected_materials_withoout_accepted_materials(self):
item = make_item("Test Item with Rej Material Valuation WO Accepted", {"is_stock_item": 1})
company = "_Test Company with perpetual inventory"
@@ -5423,33 +5423,6 @@ class TestPurchaseReceipt(ERPNextTestSuite):
self.assertEqual(row.warehouse, "_Test Warehouse 1 - _TC")
self.assertEqual(row.incoming_rate, 100)
def test_bill_for_rejected_quantity_in_purchase_invoice(self):
item_code = make_item("Test Rejected Qty", {"is_stock_item": 1}).name
with self.change_settings("Buying Settings", {"bill_for_rejected_quantity_in_purchase_invoice": 0}):
pr = make_purchase_receipt(
item_code=item_code,
qty=10,
rejected_qty=2,
rate=10,
warehouse="_Test Warehouse - _TC",
)
self.assertEqual(pr.total_qty, 10)
self.assertEqual(pr.total, 100)
with self.change_settings("Buying Settings", {"bill_for_rejected_quantity_in_purchase_invoice": 1}):
pr = make_purchase_receipt(
item_code=item_code,
qty=10,
rejected_qty=2,
rate=10,
warehouse="_Test Warehouse - _TC",
)
self.assertEqual(pr.total_qty, 12)
self.assertEqual(pr.total, 120)
def test_different_exchange_rate_in_pr_and_pi(self):
from erpnext.accounts.doctype.account.test_account import create_account
@@ -5514,6 +5487,147 @@ class TestPurchaseReceipt(ERPNextTestSuite):
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", original_value
)
def test_purchase_receipt_gl_entries_for_asset_item(self):
from erpnext.assets.doctype.asset.test_asset import create_fixed_asset_item
# Create a Company without Stock Accounts Linked.
company = frappe.get_doc(
{
"doctype": "Company",
"company_name": "Asset Company",
"country": "India",
"default_currency": "INR",
}
).insert()
stock_accounts = (
company.default_inventory_account,
company.stock_adjustment_account,
company.stock_received_but_not_billed,
)
company.update(
{"stock_in_hand_account": "", "stock_adjustment_account": "", "stock_received_but_not_billed": ""}
).save()
for account in stock_accounts:
frappe.db.delete("Account", account)
asset_category = create_asset_category_for_pr_test()
asset_item = create_fixed_asset_item(
item_code="Test Fixed Asset Item for PR GL Test", asset_category=asset_category.name
)
arnb_account = frappe.db.get_value("Company", company.name, "asset_received_but_not_billed")
# Purchase Receipt should be able to create even without any stock accounts linked to company
pr = make_purchase_receipt(
item_code=asset_item.name, warehouse="Stores - AC", qty=1, rate=10000, company=company.name
)
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertTrue(gl_entries)
gl_accounts = [d.account for d in gl_entries]
# The fixed asset account set on the item row must be debited
asset_expense_account = pr.items[0].expense_account
self.assertIn(asset_expense_account, gl_accounts)
# Asset Received But Not Billed must be credited
self.assertIn(arnb_account, gl_accounts)
# No Stock-type account should appear — the inventory account map is not
# needed and must not be consulted for an asset-only receipt
for entry in gl_entries:
account_type = frappe.db.get_value("Account", entry.account, "account_type")
self.assertNotEqual(account_type, "Stock")
pr.cancel()
def test_purchase_receipt_gl_entries_with_mixed_asset_and_stock_items(self):
from erpnext.assets.doctype.asset.test_asset import create_fixed_asset_item
company = frappe.get_doc(
{
"doctype": "Company",
"company_name": "Asset Company",
"country": "India",
"default_currency": "INR",
}
).insert()
asset_category = create_asset_category_for_pr_test()
asset_item = create_fixed_asset_item(
item_code="Test Fixed Asset Item for PR GL Test", asset_category=asset_category.name
)
arnb_account = frappe.db.get_value("Company", company.name, "asset_received_but_not_billed")
pr = make_purchase_receipt(
item_code=asset_item.name,
qty=1,
rate=10000,
warehouse="Stores - AC",
do_not_save=True,
company=company.name,
)
pr.append(
"items",
{
"item_code": "_Test Item",
"warehouse": "Stores - AC",
"qty": 5,
"received_qty": 5,
"rejected_qty": 0,
"rate": 50,
"uom": "_Test UOM",
"stock_uom": "_Test UOM",
"conversion_factor": 1.0,
"cost_center": frappe.get_cached_value("Company", pr.company, "cost_center"),
},
)
pr.insert()
pr.submit()
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertTrue(gl_entries)
gl_accounts = [d.account for d in gl_entries]
self.assertIn(arnb_account, gl_accounts)
# The fixed asset account set on the item row must be debited
asset_expense_account = pr.items[0].expense_account
self.assertIn(asset_expense_account, gl_accounts)
# Asset Received But Not Billed must be credited
self.assertIn(asset_category.accounts[0].fixed_asset_account, gl_accounts)
# Stock Accounts should be used for Stock Items
self.assertIn(company.stock_received_but_not_billed, gl_accounts)
self.assertIn(company.default_inventory_account, gl_accounts)
pr.cancel()
def create_asset_category_for_pr_test():
category_name = "Test Asset Category for PR"
asset_category = frappe.get_doc(
{
"doctype": "Asset Category",
"asset_category_name": category_name,
"enable_cwip_accounting": 0,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 12,
"frequency_of_depreciation": 1,
"accounts": [
{
"company_name": "Asset Company",
"fixed_asset_account": "Electronic Equipment - AC",
}
],
}
).insert()
return asset_category
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -852,7 +852,7 @@ class StockEntry(StockController, SubcontractingInwardController):
else:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
if self.purpose == "Manufacture":
if self.purpose in ["Manufacture", "Repack"]:
if d.is_finished_item or d.type or d.is_legacy_scrap_item:
d.s_warehouse = None
if not d.t_warehouse:

View File

@@ -83,6 +83,7 @@ class StockReconciliation(StockController):
self.set_total_qty_and_amount()
self.validate_putaway_capacity()
self.validate_inventory_dimension()
self.validate_uom_is_integer("stock_uom", "qty")
if self._action == "submit":
self.validate_reserved_stock()

View File

@@ -1,5 +1,6 @@
{
"actions": [],
"allow_bulk_edit": 1,
"allow_rename": 1,
"beta": 1,
"creation": "2021-10-01 10:56:30.814787",
@@ -13,6 +14,8 @@
"end_time",
"limits_dont_apply_on",
"item_based_reposting",
"column_break_mavd",
"do_not_fetch_incoming_rate_from_serial_no",
"section_break_dxuf",
"enable_parallel_reposting",
"no_of_parallel_reposting",
@@ -99,13 +102,23 @@
"fieldname": "enable_separate_reposting_for_gl",
"fieldtype": "Check",
"label": "Enable Separate Reposting for GL"
},
{
"fieldname": "column_break_mavd",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "For legacy serial nos, do not fetch incoming rate from serial no and calculate it based on the inward transaction",
"fieldname": "do_not_fetch_incoming_rate_from_serial_no",
"fieldtype": "Check",
"label": "Do not fetch incoming rate from Serial No"
}
],
"hide_toolbar": 0,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-03-16 13:28:20.978007",
"modified": "2026-05-15 12:59:34.392491",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reposting Settings",

View File

@@ -16,6 +16,7 @@ class StockRepostingSettings(Document):
if TYPE_CHECKING:
from frappe.types import DF
do_not_fetch_incoming_rate_from_serial_no: DF.Check
enable_parallel_reposting: DF.Check
enable_separate_reposting_for_gl: DF.Check
end_time: DF.Time | None

View File

@@ -7,6 +7,7 @@
"engine": "InnoDB",
"field_order": [
"uom",
"column_break_nmeg",
"conversion_factor"
],
"fields": [
@@ -27,12 +28,16 @@
"non_negative": 1,
"oldfieldname": "conversion_factor",
"oldfieldtype": "Float"
},
{
"fieldname": "column_break_nmeg",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-11-19 21:27:13.968771",
"modified": "2026-04-27 02:22:52.652036",
"modified_by": "Administrator",
"module": "Stock",
"name": "UOM Conversion Detail",

View File

@@ -1129,7 +1129,7 @@ def insert_item_price(ctx: ItemDetailsCtx):
)
item_price.insert()
frappe.msgprint(
_("Item Price Added for {0} in Price List {1}").format(
_("Item Price added for {0} in Price List - {1}").format(
get_link_to_form("Item", ctx.item_code), ctx.price_list
),
alert=True,
@@ -1153,9 +1153,10 @@ def insert_item_price(ctx: ItemDetailsCtx):
)
item_price.insert()
frappe.msgprint(
_("Item Price added for {0} in Price List {1}").format(
_("Item Price added for {0} in Price List - {1}").format(
get_link_to_form("Item", ctx.item_code), ctx.price_list
)
),
alert=True,
)

View File

@@ -1069,6 +1069,12 @@ class SerialBatchCreation:
self.__dict__.update(item_details)
def set_other_details(self):
from erpnext.stock.utils import get_combine_datetime
if not self.get("posting_datetime"):
if self.get("posting_date") and self.get("posting_time"):
self.posting_datetime = get_combine_datetime(self.posting_date, self.posting_time)
if not self.get("posting_datetime"):
self.posting_datetime = now()
self.__dict__["posting_datetime"] = self.posting_datetime

View File

@@ -2433,7 +2433,9 @@ def get_stock_value_difference(
)
if voucher_detail_no:
query = query.where(table.voucher_detail_no != voucher_detail_no)
query = query.where(
(table.voucher_detail_no != voucher_detail_no) | (table.voucher_detail_no.isnull())
)
elif voucher_no:
query = query.where(table.voucher_no != voucher_no)

View File

@@ -80,14 +80,13 @@
{
"child": 1,
"collapsible": 1,
"filters": "[[\"Sales Invoice\",\"is_return\",\"=\",1]]",
"icon": "",
"indent": 0,
"keep_closed": 0,
"label": "Credit Note",
"link_to": "Sales Invoice",
"link_type": "DocType",
"route_options": "",
"route_options": "{\"is_return\": 1}",
"show_arrow": 0,
"type": "Link"
},
@@ -139,12 +138,12 @@
{
"child": 1,
"collapsible": 1,
"filters": "[[\"Purchase Invoice\",\"is_return\",\"=\",1]]",
"indent": 0,
"keep_closed": 0,
"label": "Debit Note",
"link_to": "Purchase Invoice",
"link_type": "DocType",
"route_options": "{\"is_return\": 1}",
"show_arrow": 0,
"type": "Link"
},