Compare commits

...

507 Commits

Author SHA1 Message Date
Frappe PR Bot
36bc8fe3f0 chore(release): Bumped to Version 15.76.0
# [15.76.0](https://github.com/frappe/erpnext/compare/v15.75.1...v15.76.0) (2025-08-19)

### Bug Fixes

* add fieldname in accounting dimension filter ([ac4a5bf](ac4a5bfe6d))
* add patch ([a853010](a853010537))
* add validation for draft PR/PI in Asset ([4187e60](4187e60b07))
* add value adjustment amount in asset value ([89ad9f1](89ad9f1bb4))
* add value adjustment amount in report for group by category filter ([089007f](089007f88a))
* additional cost not consider in valuation rate for Stock Entry transfer ([e4398d3](e4398d3761))
* apply grand total to default payment mode in Sales and POS invoices ([605f513](605f513ce3))
* **asset:** prevent translation function shadowing in make_journal_entry ([1af8ab2](1af8ab2a3b))
* company issue in setup wizard ([cea4b50](cea4b50bbc))
* current qty for batch in stock reco ([3ecb09a](3ecb09ae52))
* fetch fieldname in accounting dimension filter ([d494d8c](d494d8c299))
* formatted string for disabled filter in get_income_account ([c9f79b3](c9f79b3ba9))
* handle default accounting dimension ([e50d6c6](e50d6c6b62))
* handle default dimension for all company ([0cd45a0](0cd45a0022))
* handle empty loyalty point details ([dc953f7](dc953f70d1))
* handle mode of payment filter (backport [#49185](https://github.com/frappe/erpnext/issues/49185)) ([#49221](https://github.com/frappe/erpnext/issues/49221)) ([b6c992f](b6c992ffeb))
* ignore links in Dunning patch (backport [#49201](https://github.com/frappe/erpnext/issues/49201)) ([#49204](https://github.com/frappe/erpnext/issues/49204)) ([07ff97f](07ff97f647))
* **pos:** include Product Bundle components in reserved qty to prevent overselling ([5ce0dc2](5ce0dc2a7a)), closes [#49021](https://github.com/frappe/erpnext/issues/49021)
* **pos:** populate packed_items table in POS Invoice ([1f3d8e8](1f3d8e8d64))
* **pos:** use packed_items snapshot for bundle reservations ([cc82836](cc82836109)), closes [#49106](https://github.com/frappe/erpnext/issues/49106)
* product bundle child item quantity should be a positive number ([f831d45](f831d45cc3))
* **quotation:** update currency on duplicate ([419f717](419f717542))
* remove unclear message related to availability of product bundle ([383744b](383744b8e4))
* sanitize column name for inventory_dimensions in get_stock_balance ([69389bb](69389bb355))
* **stock:** don't override t_warehouse if no rules found ([da3d8fb](da3d8fbbc5))
* update dunning status based on credit notes (backport [#49066](https://github.com/frappe/erpnext/issues/49066)) ([#49208](https://github.com/frappe/erpnext/issues/49208)) ([9a12c73](9a12c73e22))
* use query builder instead of raw SQL in get_blanket_orders ([4a0d7fd](4a0d7fd205))
* use query builder instead of raw SQL in get_loyalty_details ([a3c5b0a](a3c5b0a510))
* use query builder instead of raw SQL in get_material_requests_based_on_supplier ([9d0fe06](9d0fe060c8))
* use query builder instead of raw SQL in get_rfq_containing_supplier ([4ac386a](4ac386a84e))
* use query builder instead of raw SQL in get_timesheet_detail_rate ([0a2a7fa](0a2a7fa6aa))
* use query builder instead of raw SQL in unset_existing_data ([92391a6](92391a69cf))
* wrap inter-company order button labels in __() for translation ([#49178](https://github.com/frappe/erpnext/issues/49178)) ([db48635](db486356db))
* wrap inter-company order button labels in __() for translation (backport [#49178](https://github.com/frappe/erpnext/issues/49178)) ([#49179](https://github.com/frappe/erpnext/issues/49179)) ([6030eb2](6030eb2992))

### Features

* enhance barcode scanner to support warehouse scanning (backport [#48865](https://github.com/frappe/erpnext/issues/48865)) ([#49162](https://github.com/frappe/erpnext/issues/49162)) ([ad052d7](ad052d72d7))
* select child item when creating one document from another ([5f06d87](5f06d87f01))

### Reverts

* Revert "fix: use checkout@v2 instead of v4" ([e849019](e8490196ba))
2025-08-19 11:56:17 +00:00
ruthra kumar
cf86843d48 Merge pull request #49227 from frappe/version-15-hotfix
chore: release v15
2025-08-19 17:25:00 +05:30
rohitwaghchaure
696fbbb0e2 Merge pull request #49230 from frappe/mergify/bp/version-15-hotfix/pr-49228
fix: company issue in setup wizard (backport #49228)
2025-08-19 16:42:29 +05:30
rohitwaghchaure
dffa3baea6 Merge pull request #49229 from frappe/mergify/bp/version-15-hotfix/pr-49223
fix(stock): don't override t_warehouse if no rules found (backport #49223)
2025-08-19 16:27:14 +05:30
Rohit Waghchaure
cea4b50bbc fix: company issue in setup wizard
(cherry picked from commit 1fb0d1460a)
2025-08-19 10:52:49 +00:00
Kavin
da3d8fbbc5 fix(stock): don't override t_warehouse if no rules found
(cherry picked from commit 66f217c8e6)
2025-08-19 10:39:47 +00:00
ruthra kumar
dac8dc6b8a Merge pull request #49224 from frappe/mergify/bp/version-15-hotfix/pr-49102
fix(quotation): update currency on duplicate (backport #49102)
2025-08-19 14:10:21 +05:30
ravibharathi656
419f717542 fix(quotation): update currency on duplicate
(cherry picked from commit 430a06d056)
2025-08-19 08:35:09 +00:00
Khushi Rawat
80d9181b29 Merge pull request #49215 from frappe/mergify/bp/version-15-hotfix/pr-49141
refactor: consider asset value adjustments in report (backport #49141)
2025-08-19 13:06:54 +05:30
ruthra kumar
b6c992ffeb fix: handle mode of payment filter (backport #49185) (#49221)
Merge pull request #49185 from aerele/mop-sales-register

fix: handle mode of payment filter
(cherry picked from commit d656e02441)

Co-authored-by: Logesh Periyasamy <logeshperiyasamy24@gmail.com>
2025-08-19 12:26:25 +05:30
Logesh Periyasamy
cfb7558465 Merge pull request #49185 from aerele/mop-sales-register
fix: handle mode of payment filter
(cherry picked from commit d656e02441)
2025-08-19 06:33:41 +00:00
ruthra kumar
2cfdb2d2cd Merge pull request #49218 from frappe/mergify/bp/version-15-hotfix/pr-49055
fix: fetch fieldname in accounting dimension filter (backport #49055)
2025-08-19 11:24:52 +05:30
Diptanil Saha
529a84154b Merge pull request #49219 from frappe/mergify/bp/version-15-hotfix/pr-49192
fix: improve queries with query builder and input sanitization (backport #49192)
2025-08-19 11:16:02 +05:30
ruthra kumar
4af814dc3b chore: resolve conflicts 2025-08-19 11:06:23 +05:30
diptanilsaha
dc953f70d1 fix: handle empty loyalty point details
(cherry picked from commit 1231ca17c9)
2025-08-19 05:25:19 +00:00
diptanilsaha
0a2a7fa6aa fix: use query builder instead of raw SQL in get_timesheet_detail_rate
(cherry picked from commit e563ed0c75)
2025-08-19 05:25:19 +00:00
diptanilsaha
4ac386a84e fix: use query builder instead of raw SQL in get_rfq_containing_supplier
(cherry picked from commit 7f2a52ff71)
2025-08-19 05:25:18 +00:00
diptanilsaha
92391a69cf fix: use query builder instead of raw SQL in unset_existing_data
(cherry picked from commit 7fa4ed6139)
2025-08-19 05:25:18 +00:00
diptanilsaha
69389bb355 fix: sanitize column name for inventory_dimensions in get_stock_balance
(cherry picked from commit eb22794f14)
2025-08-19 05:25:18 +00:00
diptanilsaha
4a0d7fd205 fix: use query builder instead of raw SQL in get_blanket_orders
(cherry picked from commit 1db135262d)
2025-08-19 05:25:18 +00:00
diptanilsaha
c9f79b3ba9 fix: formatted string for disabled filter in get_income_account
(cherry picked from commit 6320f7290f)
2025-08-19 05:25:17 +00:00
diptanilsaha
9d0fe060c8 fix: use query builder instead of raw SQL in get_material_requests_based_on_supplier
(cherry picked from commit de919568b4)
2025-08-19 05:25:17 +00:00
diptanilsaha
a3c5b0a510 fix: use query builder instead of raw SQL in get_loyalty_details
(cherry picked from commit 8696ba2f5d)
2025-08-19 05:25:17 +00:00
l0gesh29
0cd45a0022 fix: handle default dimension for all company
(cherry picked from commit 77021fff74)
2025-08-19 05:19:01 +00:00
l0gesh29
e50d6c6b62 fix: handle default accounting dimension
(cherry picked from commit 16e440f9a7)
2025-08-19 05:19:01 +00:00
l0gesh29
a853010537 fix: add patch
(cherry picked from commit 3cf765d985)

# Conflicts:
#	erpnext/patches.txt
2025-08-19 05:19:00 +00:00
l0gesh29
d494d8c299 fix: fetch fieldname in accounting dimension filter
(cherry picked from commit 42f9d27d79)
2025-08-19 05:18:59 +00:00
l0gesh29
ac4a5bfe6d fix: add fieldname in accounting dimension filter
(cherry picked from commit ac2acc535d)

# Conflicts:
#	erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json
2025-08-19 05:18:59 +00:00
khushi8112
089007f88a fix: add value adjustment amount in report for group by category filter
(cherry picked from commit cd2bab7c5f)
2025-08-18 21:28:42 +00:00
khushi8112
89ad9f1bb4 fix: add value adjustment amount in asset value
(cherry picked from commit f8050f4278)
2025-08-18 21:28:42 +00:00
Diptanil Saha
d14391fb6f Merge pull request #49210 from diptanilsaha/skip_gt_default_mop
fix: apply grand total to default payment mode in Sales and POS invoices
2025-08-18 17:27:02 +05:30
mergify[bot]
9a12c73e22 fix: update dunning status based on credit notes (backport #49066) (#49208)
Co-authored-by: Karm Soni <karmdsoni8159@gmail.com>
Co-authored-by: Sagar Vora <16315650+sagarvora@users.noreply.github.com>
2025-08-18 11:51:24 +00:00
diptanilsaha
605f513ce3 fix: apply grand total to default payment mode in Sales and POS invoices 2025-08-18 17:04:38 +05:30
Diptanil Saha
fb34991980 Merge pull request #49207 from frappe/mergify/bp/version-15-hotfix/pr-49169
fix(asset): prevent translation function shadowing in make_journal_entry (backport #49169)
2025-08-18 15:16:37 +05:30
l0gesh29
1af8ab2a3b fix(asset): prevent translation function shadowing in make_journal_entry
(cherry picked from commit 5e82de1b71)
2025-08-18 09:26:40 +00:00
mergify[bot]
07ff97f647 fix: ignore links in Dunning patch (backport #49201) (#49204)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: ignore links in Dunning patch (#49201)
2025-08-17 18:52:55 +00:00
Mihir Kandoi
b49f85b877 Merge pull request #49197 from mihir-kandoi/version-15-hotfix 2025-08-17 14:27:00 +05:30
Mihir Kandoi
e8490196ba Revert "fix: use checkout@v2 instead of v4"
This reverts commit c9d69d9629.
2025-08-17 13:01:58 +05:30
rohitwaghchaure
bc1c57a18a Merge pull request #49195 from frappe/mergify/bp/version-15-hotfix/pr-49193
fix: additional cost not consider in valuation rate for Stock Entry transfer (backport #49193)
2025-08-17 11:15:19 +05:30
rohitwaghchaure
016948804d chore: fix test case 2025-08-17 10:55:54 +05:30
rohitwaghchaure
41e7463412 chore: fix conflicts 2025-08-17 10:54:46 +05:30
Rohit Waghchaure
e4398d3761 fix: additional cost not consider in valuation rate for Stock Entry transfer
(cherry picked from commit bbc772abe7)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/test_stock_entry.py
2025-08-17 05:22:52 +00:00
Mihir Kandoi
6030eb2992 fix: wrap inter-company order button labels in __() for translation (backport #49178) (#49179)
fix: wrap inter-company order button labels in __() for translation (#49178)

* Updated purchase_order.js

* Update sales_order.js

(cherry picked from commit 078b8439d9)

Co-authored-by: divyalekha99 <32547248+divyalekha99@users.noreply.github.com>
2025-08-14 20:33:32 +05:30
divyalekha99
db486356db fix: wrap inter-company order button labels in __() for translation (#49178)
* Updated purchase_order.js

* Update sales_order.js

(cherry picked from commit 078b8439d9)
2025-08-14 15:00:01 +00:00
Diptanil Saha
464f504b61 Merge pull request #49177 from frappe/mergify/bp/version-15-hotfix/pr-49108
fix(pos): include Product Bundle components in reserved qty to preven… (backport #49108)
2025-08-14 19:09:42 +05:30
Diptanil Saha
84fda4afb5 Merge pull request #49176 from frappe/mergify/bp/version-15-hotfix/pr-49163
fix: product bundle child item quantity should be a positive number (backport #49163)
2025-08-14 19:08:53 +05:30
Lewis
93ad17ac7b chore: improve code clarity per reviewer feedback
- Rename stock_qty variable to reserved_qty for clarity
- Update get_pos_reserved_qty_from_table to return float
- Simplify aggregation logic in get_pos_reserved_qty
- Ensure return values match docstring specifications

(cherry picked from commit 54d3e5675f)
2025-08-14 13:19:28 +00:00
diptanilsaha
f831d45cc3 fix: product bundle child item quantity should be a positive number
(cherry picked from commit 711076d02d)
2025-08-14 13:19:27 +00:00
Lewis
cc82836109 fix(pos): use packed_items snapshot for bundle reservations
Replaced live Product Bundle queries with `Packed Item` table
lookups to ensure historical reservation accuracy.
Addresses bundle qty underestimation and avoids errors when
bundle definitions change after sale.

Inspired by approach from @diptanilsaha in #49106.

(cherry picked from commit d77d79e011)
2025-08-14 13:19:27 +00:00
Lewis
383744b8e4 fix: remove unclear message related to availability of product bundle
(cherry picked from commit f5e5f7b588)
2025-08-14 13:19:27 +00:00
Lewis
1f3d8e8d64 fix(pos): populate packed_items table in POS Invoice
(cherry picked from commit a65b200eb7)
2025-08-14 13:19:27 +00:00
Lewis
c9902eed72 chore: apply pre-commit formatting
(cherry picked from commit 0fc187adc3)
2025-08-14 13:19:26 +00:00
Lewis Mojica
0d793c11a1 chore: remove unused variable
(cherry picked from commit 5a5804ca87)
2025-08-14 13:19:26 +00:00
Lewis
5ce0dc2a7a fix(pos): include Product Bundle components in reserved qty to prevent overselling
- Add `get_bundle_pos_reserved_qty` to account for component items in submitted POS Invoices
- Update `get_pos_reserved_qty` to sum direct and bundle reservations
- Remove double subtraction in `get_bundle_availability` to avoid underestimating bundle availability
- Prevents overselling when multiple POS sessions sell bundles with shared components
- Fixes #49021

(cherry picked from commit 984d744ac2)
2025-08-14 13:19:26 +00:00
mergify[bot]
ad052d72d7 feat: enhance barcode scanner to support warehouse scanning (backport #48865) (#49162)
Co-authored-by: Sagar Vora <16315650+sagarvora@users.noreply.github.com>
Co-authored-by: Soni Karm <93865733+karm1000@users.noreply.github.com>
2025-08-14 13:20:01 +05:30
Khushi Rawat
8d9095def0 Merge pull request #49146 from frappe/mergify/bp/version-15-hotfix/pr-49075
fix: add validation for draft PR/PI in Asset (backport #49075)
2025-08-14 12:10:00 +05:30
Frappe PR Bot
aaeb3ab06c chore(release): Bumped to Version 15.75.1
## [15.75.1](https://github.com/frappe/erpnext/compare/v15.75.0...v15.75.1) (2025-08-14)

### Bug Fixes

* current qty for batch in stock reco ([de641d7](de641d7f68))
2025-08-14 06:03:46 +00:00
rohitwaghchaure
1975ec3f76 Merge pull request #49159 from frappe/mergify/bp/version-15/pr-49158
chore: convert message to toast notification (backport #49155) (backport #49158)
2025-08-14 11:32:20 +05:30
rohitwaghchaure
b4533ee362 Merge pull request #49160 from frappe/mergify/bp/version-15/pr-49156
fix: current qty for batch in stock reco (backport #49154) (backport #49156)
2025-08-14 11:32:01 +05:30
Rohit Waghchaure
de641d7f68 fix: current qty for batch in stock reco
(cherry picked from commit 817e719cc2)
(cherry picked from commit 3ecb09ae52)
2025-08-14 05:27:13 +00:00
Rohit Waghchaure
02735e69dd chore: convert message to toast notification
(cherry picked from commit fc71001110)
(cherry picked from commit 7a04bf85bc)
2025-08-14 05:26:23 +00:00
rohitwaghchaure
ae22a90160 Merge pull request #49158 from frappe/mergify/bp/version-15-hotfix/pr-49155
chore: convert message to toast notification (backport #49155)
2025-08-14 10:55:28 +05:30
Rohit Waghchaure
7a04bf85bc chore: convert message to toast notification
(cherry picked from commit fc71001110)
2025-08-14 05:06:00 +00:00
rohitwaghchaure
ef0f91f862 Merge pull request #49156 from frappe/mergify/bp/version-15-hotfix/pr-49154
fix: current qty for batch in stock reco (backport #49154)
2025-08-14 08:31:20 +05:30
Rohit Waghchaure
3ecb09ae52 fix: current qty for batch in stock reco
(cherry picked from commit 817e719cc2)
2025-08-13 18:28:29 +00:00
Khushi Rawat
9c5dacd977 refactor: validate linked purchase docs before field usage
(cherry picked from commit 4a48b13715)
2025-08-13 11:49:35 +00:00
Rehan Ansari
4187e60b07 fix: add validation for draft PR/PI in Asset
(cherry picked from commit 4cf481cca8)
2025-08-13 11:49:35 +00:00
Mihir Kandoi
69eb31028a Merge pull request #49138 from frappe/mergify/bp/version-15-hotfix/pr-49134
chore: add default value for posting_time field in subcontracting receipt (backport #49134)
2025-08-13 14:21:41 +05:30
Karm Soni
15a104b0f8 chore: add default value for posting_time field in subcontracting receipt
(cherry picked from commit b7470617e0)
2025-08-13 08:35:45 +00:00
Mihir Kandoi
6b436e1e71 Merge pull request #49124 from frappe/mergify/bp/version-15-hotfix/pr-49122
feat: select child item when creating one document from another (backport #49122)
2025-08-12 21:53:39 +05:30
Mihir Kandoi
5f06d87f01 feat: select child item when creating one document from another
(cherry picked from commit a9936ae133)
2025-08-12 16:07:51 +00:00
Frappe PR Bot
ed50c3d896 chore(release): Bumped to Version 15.75.0
# [15.75.0](https://github.com/frappe/erpnext/compare/v15.74.0...v15.75.0) (2025-08-12)

### Bug Fixes

* avoid property setter for custom field ([faae734](faae734797))
* handle case where taxes is added invoice changed to non-export later ([90913c6](90913c66ae))
* handle negative inventory check ([#48558](https://github.com/frappe/erpnext/issues/48558)) ([#48691](https://github.com/frappe/erpnext/issues/48691)) ([9da2be2](9da2be2325))
* Pick List barcode scanner and manual picking issues ([38c886d](38c886db8b))
* **process statement of accounts:** use date instead of formatted date ([6ad3461](6ad3461953))
* **regional-uae:** mark export items as zero rated ([9df6424](9df6424a20))
* **regional-uae:** restrict zero rated export to invoice ([62db42c](62db42cf2f))
* **regional-uae:** split export determination ([106b83e](106b83e9f9))
* show message only if no tax is applied ([614d38d](614d38d0e6))
* show name of the employee in general ledger report ([0e7f778](0e7f778b3f))
* simplify export determination logic ([68c6586](68c65866bf))
* table render issue on pop-up edit ([52db89f](52db89f73f))

### Features

* add customer name column in gross profit report ([9cd6053](9cd60531d2))
* add item_name column to Material Request dialog in Purchase Order ([eafe33a](eafe33a176))
* add party name column in general ledger report ([680fa3b](680fa3b8f3))
* add party name in GL entries ([3763ad4](3763ad451b))

### Performance Improvements

* multiple performance fixes in `get_item_warehouse` (backport [#49118](https://github.com/frappe/erpnext/issues/49118)) ([76b0f4f](76b0f4fb25))
2025-08-12 12:04:27 +00:00
ruthra kumar
85bb086e90 Merge pull request #49116 from frappe/version-15-hotfix
chore: release v15
2025-08-12 17:33:04 +05:30
mergify[bot]
76b0f4fb25 perf: multiple performance fixes in get_item_warehouse (backport #49118)
Co-authored-by: Sagar Vora <16315650+sagarvora@users.noreply.github.com>
2025-08-12 11:43:43 +00:00
ruthra kumar
167ff00c99 Merge pull request #48999 from frappe/mergify/bp/version-15-hotfix/pr-48761
fix: prevent gain or loss entry cancellation upon reposting (backport #48761)
2025-08-12 17:03:12 +05:30
Logesh Periyasamy
6e7fb2ea01 Merge pull request #48761 from aerele/exchange-gain-or-loss-on-repost
fix: prevent gain or loss entry cancellation upon reposting
(cherry picked from commit a8d17b7590)
2025-08-12 16:47:07 +05:30
ruthra kumar
daf3aab6ae Merge pull request #49111 from frappe/mergify/bp/version-15-hotfix/pr-49086
fix(process statement of accounts): use date instead of formatted date (backport #49086)
2025-08-12 11:39:14 +05:30
ruthra kumar
1c9a74f81c Merge pull request #49110 from frappe/mergify/bp/version-15-hotfix/pr-49096
fix: table render issue on pop-up edit (backport #49096)
2025-08-12 10:49:38 +05:30
ruthra kumar
2eaffa119f Merge pull request #48715 from wojosc/patch-44
refactor: remove default value for opportunity type
2025-08-12 10:49:16 +05:30
ravibharathi656
6ad3461953 fix(process statement of accounts): use date instead of formatted date
(cherry picked from commit aa3f50ab77)
2025-08-12 05:18:09 +00:00
l0gesh29
52db89f73f fix: table render issue on pop-up edit
(cherry picked from commit 3431c6c90e)
2025-08-12 05:10:22 +00:00
Khushi Rawat
4f72635698 Merge pull request #49100 from frappe/mergify/bp/version-15-hotfix/pr-48862
feat: show party name in reports (backport #48862)
2025-08-11 18:25:50 +05:30
khushi8112
0e7f778b3f fix: show name of the employee in general ledger report
(cherry picked from commit 70411ec086)
2025-08-11 12:24:45 +00:00
khushi8112
d6e9216a45 chore: code format
(cherry picked from commit b0c0a86fcf)
2025-08-11 12:24:45 +00:00
khushi8112
9cd60531d2 feat: add customer name column in gross profit report
(cherry picked from commit 9dee411eb5)
2025-08-11 12:24:44 +00:00
khushi8112
3763ad451b feat: add party name in GL entries
(cherry picked from commit 3d94a7cf2c)
2025-08-11 12:24:44 +00:00
khushi8112
680fa3b8f3 feat: add party name column in general ledger report
(cherry picked from commit 5f24061dd4)
2025-08-11 12:24:44 +00:00
ruthra kumar
0644c78c2e Merge pull request #49097 from frappe/mergify/bp/version-15-hotfix/pr-49093
fix: allow creation of root accounts in account tree view  (backport #49093)
2025-08-11 17:44:51 +05:30
Lakshit Jain
c2ad5d0fc7 Merge pull request #49093 from ljain112/fix-account-coa
fix: allow creation of root accounts in account tree view

(cherry picked from commit d45cd5af2b)
2025-08-11 11:38:30 +00:00
Asmita Hase
6de74697c9 Merge pull request #49095 from frappe/mergify/bp/version-15-hotfix/pr-49088
fix: Include Employee party type in Receivable and Payable account filters (backport #49088)
2025-08-11 17:01:48 +05:30
Wolfram Schmidt
36197afa19 refactor: remove default value for opportunity type
removing default value as this leads to an error in non-english setups
2025-08-11 16:54:56 +05:30
Assem Bahnasy
e762007e0e refactor: Move Employee inclusion to SQL level to preserve search semantics
(cherry picked from commit 8a9bf166c6)
2025-08-11 11:15:19 +00:00
Assem Bahnasy
c462219dd7 refactor: Use parameterized SQL queries to prevent injection and handle None values
(cherry picked from commit a08c7f37d3)
2025-08-11 11:15:19 +00:00
ruthra kumar
0cd9330e44 Merge pull request #49085 from frappe/mergify/bp/version-15-hotfix/pr-48730
fix(regional-uae): mark export items as zero rated (backport #48730)
2025-08-11 12:35:56 +05:30
ruthra kumar
faba523086 chore: resolve conflict 2025-08-11 11:41:10 +05:30
Dany Robert
90913c66ae fix: handle case where taxes is added invoice changed to non-export later
(cherry picked from commit 29c3ef8280)
2025-08-11 06:06:08 +00:00
Dany Robert
d47c25287d chore: code styling
(cherry picked from commit c8940a39b3)
2025-08-11 06:06:08 +00:00
Dany Robert
614d38d0e6 fix: show message only if no tax is applied
(cherry picked from commit 38471995e7)
2025-08-11 06:06:08 +00:00
Dany Robert
534b27afa5 chore: linters
(cherry picked from commit eb6c8d8938)
2025-08-11 06:06:07 +00:00
Dany Robert
faae734797 fix: avoid property setter for custom field
(cherry picked from commit 0c15b65756)
2025-08-11 06:06:07 +00:00
Dany Robert
68c65866bf fix: simplify export determination logic
(cherry picked from commit d25846f383)
2025-08-11 06:06:07 +00:00
Dany Robert
62db42cf2f fix(regional-uae): restrict zero rated export to invoice
(cherry picked from commit 1170e4fb2c)
2025-08-11 06:06:06 +00:00
Dany Robert
106b83e9f9 fix(regional-uae): split export determination
(cherry picked from commit dc72e6cf36)
2025-08-11 06:06:06 +00:00
Dany Robert
9df6424a20 fix(regional-uae): mark export items as zero rated
(cherry picked from commit b8224693c4)

# Conflicts:
#	erpnext/patches.txt
2025-08-11 06:06:06 +00:00
Mihir Kandoi
474ddbae0c Merge pull request #49083 from frappe/mergify/bp/version-15-hotfix/pr-48691
fix: handle negative inventory check (#48558) (backport #48691)
2025-08-11 10:45:20 +05:30
Vishist16
9da2be2325 fix: handle negative inventory check (#48558) (#48691)
* fix: handle negative inventory check (#48558)

* fix: updated DocField via Desk UI as suggested

* fix: update DocField via Desk UI and fix linting issues

(cherry picked from commit 3ee23d9ee8)
2025-08-11 05:00:20 +00:00
mergify[bot]
15e8fa3189 Merge pull request #49081 from frappe/mergify/bp/version-15-hotfix/pr-49068
fix(job-card): Add filter to item_code query for scrap_items to exclude disabled (backport #49068)
2025-08-11 10:25:21 +05:30
Mihir Kandoi
02d2ad6442 Merge pull request #49080 from frappe/mergify/bp/version-15-hotfix/pr-49071
feat: add item_name column to Material Request dialog in Purchase Order (backport #49071)
2025-08-11 10:11:47 +05:30
navinrc
eafe33a176 feat: add item_name column to Material Request dialog in Purchase Order
(cherry picked from commit 4312719010)
2025-08-11 04:33:31 +00:00
rohitwaghchaure
bb2c21be44 Merge pull request #49029 from IMS94/fix/pick-list-barcode-scanner-serial-assignment-hotfix
fix: Pick List barcode scanner and manual picking issues
2025-08-08 19:53:21 +05:30
Frappe PR Bot
d4519e5d3d chore(release): Bumped to Version 15.74.0
# [15.74.0](https://github.com/frappe/erpnext/compare/v15.73.2...v15.74.0) (2025-08-08)

### Bug Fixes

* add condition to fetch active accounts ([0d47eb1](0d47eb1fa0))
* added a flag on journal entry to ignore party account type validation if required ([8a3fdb4](8a3fdb4ec2))
* dont validate account type in for employee party type ([d3dde83](d3dde833f7))
* nonetype error on applying presentation_currency filter on financial statements and trial balance report ([24ca7bb](24ca7bb64f))
* **purchase invoice:** filter only enabled account ([b060cdb](b060cdb4f5))
* stock reservation Delivered Qty against the batch ([849f646](849f646bd2))
* timeout while submitting purchase receipt ([e693ab7](e693ab76fa))
* zero valuation rate for the batch ([90a0873](90a0873044))

### Features

* added chart of accounts and tax template for australian localisation ([#48208](https://github.com/frappe/erpnext/issues/48208)) ([36cb5b6](36cb5b6589))
2025-08-08 09:20:20 +00:00
Asmita Hase
1fceebd0a8 Merge pull request #49058 from frappe/version-15-hotfix 2025-08-08 14:48:56 +05:30
Asmita Hase
418d14ecc9 Merge pull request #49054 from frappe/mergify/bp/version-15-hotfix/pr-49053
fix: dont validate account type while creating journal entry for employee party type (backport #49053)
2025-08-08 14:19:17 +05:30
Asmita Hase
d3dde833f7 fix: dont validate account type in for employee party type
(cherry picked from commit e7a2ff1884)
2025-08-08 08:28:37 +00:00
Asmita Hase
0e56c47a4c Merge pull request #49052 from frappe/mergify/bp/version-15-hotfix/pr-49051 2025-08-08 13:56:38 +05:30
Asmita Hase
5f2725f61f chore: trigger create_advance_payment_ledger_records patch
(cherry picked from commit 6d908f44a5)
2025-08-08 07:29:33 +00:00
Mihir Kandoi
b45d74c56e Merge pull request #49049 from frappe/mergify/bp/version-15-hotfix/pr-48813
fix(purchase invoice): filter only enabled account (backport #48813)
2025-08-08 11:11:43 +05:30
mithili
90e583db19 chore: add back filter
(cherry picked from commit 23308f6d10)
2025-08-08 05:25:04 +00:00
mithili
0d47eb1fa0 fix: add condition to fetch active accounts
(cherry picked from commit 7c8dd86a35)
2025-08-08 05:25:04 +00:00
mithili
b060cdb4f5 fix(purchase invoice): filter only enabled account
(cherry picked from commit c3111db6e2)
2025-08-08 05:25:04 +00:00
Asmita Hase
09c96d6a83 Merge pull request #49044 from frappe/mergify/bp/version-15-hotfix/pr-49042
fix: flag on journal entry to ignore party account type validation if required (backport #49042)
2025-08-07 18:17:15 +05:30
Asmita Hase
8a3fdb4ec2 fix: added a flag on journal entry to ignore party account type validation if required
(cherry picked from commit 0665d13fd3)
2025-08-07 12:30:05 +00:00
Frappe PR Bot
e0313bb27f chore(release): Bumped to Version 15.73.2
## [15.73.2](https://github.com/frappe/erpnext/compare/v15.73.1...v15.73.2) (2025-08-07)

### Bug Fixes

* zero valuation rate for the batch ([fb126e0](fb126e0838))
2025-08-07 11:03:27 +00:00
rohitwaghchaure
04c5369792 Merge pull request #49039 from frappe/mergify/bp/version-15/pr-49022
fix: zero valuation rate for the batch (backport #49022)
2025-08-07 16:32:05 +05:30
rohitwaghchaure
a11d368465 chore: fix conflicts 2025-08-07 16:15:31 +05:30
Rohit Waghchaure
fb126e0838 fix: zero valuation rate for the batch
(cherry picked from commit c8410cb5ca)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2025-08-07 10:43:45 +00:00
rohitwaghchaure
96e467670a Merge pull request #49035 from frappe/mergify/bp/version-15-hotfix/pr-49022
fix: zero valuation rate for the batch (backport #49022)
2025-08-07 14:34:46 +05:30
rohitwaghchaure
105838091e chore: fix conflicts 2025-08-07 14:10:21 +05:30
Rohit Waghchaure
90a0873044 fix: zero valuation rate for the batch
(cherry picked from commit c8410cb5ca)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2025-08-07 08:25:51 +00:00
rohitwaghchaure
965de96251 Merge pull request #49012 from rohitwaghchaure/fixed-support-43860
fix: stock reservation Delivered Qty against the batch
2025-08-07 12:27:54 +05:30
ruthra kumar
1a57f603fb Merge pull request #49031 from frappe/mergify/bp/version-15-hotfix/pr-48208
feat: added chart of accounts and tax template for australian localisation (backport #48208)
2025-08-07 10:36:52 +05:30
Jeba Jebas
36cb5b6589 feat: added chart of accounts and tax template for australian localisation (#48208)
* Add Australian Localisation Setup

* feat: added chart of accounts and tax template for australian localisation

* chore: linter fix

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
(cherry picked from commit bb62a01c0d)
2025-08-07 04:39:17 +00:00
Imesha Sudasingha
dab6bb8e05 style: apply pre-commit formatting fixes 2025-08-07 05:15:42 +05:30
Imesha Sudasingha
eebeb36b6c refactor: combine duplicate pick_manually handlers in pick_list.js 2025-08-07 05:09:46 +05:30
Imesha Sudasingha
38c886db8b fix: Pick List barcode scanner and manual picking issues
- Fix barcode scanner serial_no_field configuration from 'not_supported' to 'serial_no'
- Add conditional serial assignment in create_pick_list to respect pick_manually flag
- Add client-side handler to clear auto-assigned data when switching to manual picking
- Add server-side validation to ensure data consistency
2025-08-07 05:04:25 +05:30
Diptanil Saha
f4f1fdedee Merge pull request #49025 from frappe/mergify/bp/version-15-hotfix/pr-49023
fix: NoneType error on applying presentation_currency filter on financial statements and trial balance report (backport #49023)
2025-08-07 02:27:43 +05:30
diptanilsaha
24ca7bb64f fix: nonetype error on applying presentation_currency filter on financial statements and trial balance report
(cherry picked from commit d7e22de44c)
2025-08-06 20:41:36 +00:00
Rohit Waghchaure
849f646bd2 fix: stock reservation Delivered Qty against the batch 2025-08-06 22:29:45 +05:30
rohitwaghchaure
e19c4c6452 Merge pull request #49019 from frappe/mergify/bp/version-15-hotfix/pr-49010
fix: timeout while submitting purchase receipt (backport #49010)
2025-08-06 22:24:17 +05:30
Rohit Waghchaure
e693ab76fa fix: timeout while submitting purchase receipt
(cherry picked from commit c433943c46)
2025-08-06 14:36:34 +00:00
Frappe PR Bot
6b38654542 chore(release): Bumped to Version 15.73.1
## [15.73.1](https://github.com/frappe/erpnext/compare/v15.73.0...v15.73.1) (2025-08-06)

### Bug Fixes

* fetch revaluated asset value for all the assets ([5224b66](5224b6677d))
2025-08-06 12:23:54 +00:00
Asmita Hase
935b8cfcf7 Merge pull request #49016 from frappe/version-15-hotfix 2025-08-06 17:52:33 +05:30
Khushi Rawat
639c75312d Merge pull request #49013 from frappe/mergify/bp/version-15-hotfix/pr-49011
fix: fetch revaluated asset value for fixed asset register (backport #49011)
2025-08-06 17:36:09 +05:30
khushi8112
e1121d1c68 chore: format code
(cherry picked from commit a33bcb47b3)
2025-08-06 11:44:55 +00:00
khushi8112
5224b6677d fix: fetch revaluated asset value for all the assets
(cherry picked from commit 67ec4fa477)
2025-08-06 11:44:55 +00:00
Asmita Hase
ad3d6a7c91 Merge pull request #49006 from frappe/mergify/bp/version-15-hotfix/pr-49003 2025-08-06 12:14:59 +05:30
Asmita Hase
2d420ed661 chore: removed uncessary comment
(cherry picked from commit b4f831a931)
2025-08-06 06:16:55 +00:00
Asmita Hase
4cc9061990 chore: add account type to employee advance account in standard chart of accounts
(cherry picked from commit a273147b6e)
2025-08-06 06:16:54 +00:00
Asmita Hase
5266690cd8 refactor: get advance payment doctypes from hooks
(cherry picked from commit 2cb2e05b19)
2025-08-06 06:16:54 +00:00
ruthra kumar
373a17e3de Merge pull request #49002 from ruthra-kumar/fix_linter_issue
chore: resolve linting issue
2025-08-06 09:57:40 +05:30
ruthra kumar
2af2002431 chore: resolve linting issue 2025-08-06 09:56:39 +05:30
Frappe PR Bot
5b4093069c chore(release): Bumped to Version 15.73.0
# [15.73.0](https://github.com/frappe/erpnext/compare/v15.72.3...v15.73.0) (2025-08-06)

### Bug Fixes

* account currency validation to exclude cancelled entries ([4ce4d34](4ce4d345e7))
* **accounts:** allow default bank account per company ([8cd90de](8cd90de70b))
* add doctype fieldname in condition ([3603cdf](3603cdf457))
* add missing parentheses ([78857cd](78857cd798))
* change modified timestamp so migrations work ([f8ea431](f8ea431551))
* do not recalculate depreciation on sale invoice cancellation for fully depreciated asset ([db41b14](db41b14317))
* do not split round off when there is a cost center allocation ([e36e502](e36e5027d7))
* enable allow_on_submit for accounting dimensions in allowed doctypes ([e22f93f](e22f93f1fb))
* failing subcontracting patch ([2f4a9f2](2f4a9f283d))
* failing subcontracting patch (backport [#48940](https://github.com/frappe/erpnext/issues/48940)) ([#48961](https://github.com/frappe/erpnext/issues/48961)) ([5a984de](5a984de697))
* include child doctypes in allow_on_submit patch for accounting dimensions ([ad56177](ad56177234))
* include child doctypes in repostable accounting types ([56dca02](56dca02cab))
* include Sales Invoice in SABB validation for packed items ([d862a74](d862a742b0))
* multiple fixes for advance payment accounting (backport [#48341](https://github.com/frappe/erpnext/issues/48341)) ([#48896](https://github.com/frappe/erpnext/issues/48896)) ([cb0addc](cb0addc122))
* payment ledger voucher seperator row currencies ([5b0486c](5b0486ca26))
* permission error on tests ([6f12029](6f12029477))
* prevent negative values in BOM fields ([#48520](https://github.com/frappe/erpnext/issues/48520), [#48662](https://github.com/frappe/erpnext/issues/48662)) (backport [#48696](https://github.com/frappe/erpnext/issues/48696)) ([#48897](https://github.com/frappe/erpnext/issues/48897)) ([5de5a8b](5de5a8bfd5))
* **process statement of accounts:** make date fields mandatory ([3e8deee](3e8deeed07))
* provide company for outstanding record. ([dc4b236](dc4b236951))
* provide missing `company` in report records that require reference to `Company:company:default_currency` ([070190d](070190d07b))
* remove api call to set default payments ([d9c1ef0](d9c1ef0926))
* server error on opportunity summary by sales stage report ([652589f](652589f636))
* set use_serial_batch_fields when creating PR from PO ([0207b82](0207b82f82))
* submit depreciation schedule only for submitted asset ([9d8cb2f](9d8cb2f57c))
* **tax withholding details:** avoid voucher duplication ([cee9f20](cee9f200ad))
* use checkout@v2 instead of v4 ([c9d69d9](c9d69d9629))
* Use correct Attachments folder in code list import ([dc7ac35](dc7ac3550e))
* use maintenance_status filter for indicators ([eec327c](eec327c02b))
* validate if journal entry linked to schedule is in draft ([37eaa07](37eaa07192))

### Features

* add 'Manufacture' section to project dashboard and show linked Work Orders ([83e9842](83e9842dd3))
* add non-negative constraint to batch size and sub operation time fields (backport [#48948](https://github.com/frappe/erpnext/issues/48948)) ([#48991](https://github.com/frappe/erpnext/issues/48991)) ([964f927](964f9275dc))
* Add non-negative constraint to completed qty fields in job card and time log (backport [#48946](https://github.com/frappe/erpnext/issues/48946)) ([#48989](https://github.com/frappe/erpnext/issues/48989)) ([84e91e0](84e91e0c7c))
* add show_amount_in_company_currency in gl report ([b964b12](b964b122ed))

### Performance Improvements

* process auto bank reconciliation in batches ([19a8dde](19a8ddef86))
* process_gl_map causing performance issues in the reposting ([a96fa55](a96fa55704))

### Reverts

* Revert "fix: set proper currency format" ([0355006](03550066a7))
2025-08-06 02:37:49 +00:00
ruthra kumar
53fef46f9a Merge pull request #48979 from frappe/version-15-hotfix
chore: release v15
2025-08-06 08:06:33 +05:30
ruthra kumar
1070322695 Merge branch 'version-15' into version-15-hotfix 2025-08-05 21:41:58 +05:30
ruthra kumar
3ea6278eb9 Merge pull request #48997 from frappe/mergify/bp/version-15-hotfix/pr-48901
fix: do not split round off when there is a cost center allocation (backport #48901)
2025-08-05 20:46:12 +05:30
ravibharathi656
e822a74479 test: add test for cost center allocation commercial rounding
(cherry picked from commit dd24cce509)

# Conflicts:
#	erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py
2025-08-05 20:29:25 +05:30
venkat102
e36e5027d7 fix: do not split round off when there is a cost center allocation
(cherry picked from commit f0df41d521)
2025-08-05 14:47:51 +00:00
ruthra kumar
f97e29058c Merge pull request #48993 from frappe/mergify/bp/version-15-hotfix/pr-48947
fix(process statement of accounts): make date fields mandatory (backport #48947)
2025-08-05 17:08:55 +05:30
mergify[bot]
964f9275dc feat: add non-negative constraint to batch size and sub operation time fields (backport #48948) (#48991)
* feat: add non-negative constraint to batch size and sub operation time fields

(cherry picked from commit f4722d3b24)

# Conflicts:
#	erpnext/manufacturing/doctype/operation/operation.json
#	erpnext/manufacturing/doctype/sub_operation/sub_operation.json

* chore: resolve conflicts

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: KerollesFathy <kerolles.f@outlook.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-08-05 11:19:46 +00:00
Mihir Kandoi
f3895e9dc9 Merge pull request #48992 from frappe/mergify/bp/version-15-hotfix/pr-48949
feat: add Manufacture section to project dashboard to show linked Work Orders (backport #48949)
2025-08-05 16:48:33 +05:30
ravibharathi656
3e8deeed07 fix(process statement of accounts): make date fields mandatory
(cherry picked from commit 23bc180d98)
2025-08-05 11:06:47 +00:00
mergify[bot]
84e91e0c7c feat: Add non-negative constraint to completed qty fields in job card and time log (backport #48946) (#48989)
* feat: Add non-negative constraint to completed qty fields in job card and time log

(cherry picked from commit c30665fda7)

# Conflicts:
#	erpnext/manufacturing/doctype/job_card/job_card.json
#	erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: KerollesFathy <kerolles.f@outlook.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-08-05 11:06:43 +00:00
KerollesFathy
83e9842dd3 feat: add 'Manufacture' section to project dashboard and show linked Work Orders
(cherry picked from commit 2729d7521d)
2025-08-05 10:59:44 +00:00
Khushi Rawat
3760ff8a7a Merge pull request #48977 from frappe/mergify/bp/version-15-hotfix/pr-48968
fix: validate asset before submitting depreciation schedule (backport #48968)
2025-08-05 16:28:03 +05:30
Khushi Rawat
ddecce400a Merge pull request #48987 from frappe/mergify/bp/version-15-hotfix/pr-48929
chore: add translation function on remarks in asset depreciation entry  (backport #48929)
2025-08-05 16:27:47 +05:30
ruthra kumar
6367bd9561 Merge pull request #48985 from frappe/mergify/bp/version-15-hotfix/pr-48798
feat: add show_amount_in_company_currency in gl report (backport #48798)
2025-08-05 16:26:37 +05:30
ruthra kumar
104569318e Merge pull request #48982 from frappe/mergify/bp/version-15-hotfix/pr-48909
fix(tax withholding details): avoid voucher duplication (backport #48909)
2025-08-05 16:25:48 +05:30
Khushi Rawat
4a8465df90 chore: resolved conflicts 2025-08-05 16:05:54 +05:30
Khushi Rawat
78857cd798 fix: add missing parentheses
(cherry picked from commit a60db40fd2)
2025-08-05 10:26:31 +00:00
Ernesto Ruiz
2c9ee7ae14 chore: add translation function on remark in setup_journal_entry_metadata in depreciation.py
(cherry picked from commit 803180d5de)

# Conflicts:
#	erpnext/assets/doctype/asset/depreciation.py
2025-08-05 10:26:31 +00:00
Ernesto Ruiz
4a5e0b181f chore: add translation function on remarks in make_journal_entry in asset.py
(cherry picked from commit 119904e44f)
2025-08-05 10:26:31 +00:00
Khushi Rawat
35f826c499 chore: remove duplicate code 2025-08-05 15:54:11 +05:30
Diptanil Saha
b565d4f0a8 Merge pull request #48983 from diptanilsaha/pos_grand_total_default_mop
refactor(pos): disable grand total to default mode of payment
2025-08-05 15:24:32 +05:30
l0gesh29
b964b122ed feat: add show_amount_in_company_currency in gl report
(cherry picked from commit 468e5e9b2e)
2025-08-05 09:52:53 +00:00
ravibharathi656
cee9f200ad fix(tax withholding details): avoid voucher duplication
(cherry picked from commit 8837016243)
2025-08-05 09:46:51 +00:00
ruthra kumar
214f15e700 Merge pull request #48972 from frappe/mergify/bp/version-15-hotfix/pr-48718
refactor: process subscriptions in batch wise (backport #48718)
2025-08-05 15:13:00 +05:30
ruthra kumar
0341941d3f Merge pull request #48973 from frappe/mergify/bp/version-15-hotfix/pr-48774
perf: process auto bank reconciliation in batches (backport #48774)
2025-08-05 15:12:46 +05:30
diptanilsaha
8d1b599d5f refactor(pos): disable grand total to default mode of payment 2025-08-05 15:09:55 +05:30
Khushi Rawat
4022fcb0d9 Merge pull request #48976 from frappe/mergify/bp/version-15-hotfix/pr-48974
fix: use maintenance_status filter for indicators (backport #48974)
2025-08-05 15:07:50 +05:30
Khushi Rawat
4f14651a42 chore: resolved conflicts 2025-08-05 15:06:57 +05:30
khushi8112
497247d89a chore: fetch docstatus to validate
(cherry picked from commit d6fb99916e)
2025-08-05 09:32:29 +00:00
khushi8112
137d2d4044 chore: add mistakenly removed test records
(cherry picked from commit f5a71c6b88)
2025-08-05 09:32:28 +00:00
khushi8112
37eaa07192 fix: validate if journal entry linked to schedule is in draft
(cherry picked from commit d5edca2022)
2025-08-05 09:32:28 +00:00
khushi8112
9d8cb2f57c fix: submit depreciation schedule only for submitted asset
(cherry picked from commit a4628c2024)

# Conflicts:
#	erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
2025-08-05 09:32:28 +00:00
khushi8112
eec327c02b fix: use maintenance_status filter for indicators
(cherry picked from commit 1b674a1051)
2025-08-05 09:32:22 +00:00
Diptanil Saha
093c94aa7c Merge pull request #48965 from frappe/mergify/bp/version-15-hotfix/pr-48964
fix: remove api call to set default payments (backport #48964)
2025-08-05 14:49:37 +05:30
ravibharathi656
19a8ddef86 perf: process auto bank reconciliation in batches
(cherry picked from commit 657de2cc7e)
2025-08-05 09:08:46 +00:00
ravibharathi656
0fdd944418 refactor: process subscriptions in batch wise
(cherry picked from commit 283d69c0bd)
2025-08-05 08:58:22 +00:00
ruthra kumar
f997393b0e Merge pull request #48971 from frappe/mergify/bp/version-15-hotfix/pr-48861
chore: correct description for `consider_party_ledger_amount` in Tax Withholding Category (backport #48861)
2025-08-05 14:25:46 +05:30
ljain112
0d02d7086c chore: correct description for consider_party_ledger_amount in Tax Withholding Category
(cherry picked from commit f619bca2d6)

# Conflicts:
#	erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
2025-08-05 14:25:23 +05:30
Mihir Kandoi
913bde0dfe Merge pull request #48967 from frappe/mergify/bp/version-15-hotfix/pr-48693
fix: Use correct Attachments folder in code list import (backport #48693)
2025-08-05 14:24:42 +05:30
Mihir Kandoi
21d33cf6b9 Merge pull request #48767 from WHWYIT/fix-issues-48765
Fix: Procurement Tracker - No permission to read Employee #48765
2025-08-05 14:20:33 +05:30
Corentin Forler
dc7ac3550e fix: Use correct Attachments folder in code list import
(cherry picked from commit bc2cb1737a)
2025-08-05 08:34:39 +00:00
Diptanil Saha
e8ba2b1576 chore: resolve conflict 2025-08-05 13:24:49 +05:30
diptanilsaha
d9c1ef0926 fix: remove api call to set default payments
(cherry picked from commit 871b8473fa)

# Conflicts:
#	erpnext/public/js/controllers/taxes_and_totals.js
2025-08-05 07:46:37 +00:00
ruthra kumar
0061bde950 Merge pull request #48962 from frappe/mergify/bp/version-15-hotfix/pr-48860
fix: add doctype fieldname in condition (backport #48860)
2025-08-05 12:17:56 +05:30
mergify[bot]
5a984de697 fix: failing subcontracting patch (backport #48940) (#48961)
* fix: failing subcontracting patch

(cherry picked from commit 14b47e81ce)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-08-05 06:37:20 +00:00
l0gesh29
3603cdf457 fix: add doctype fieldname in condition
(cherry picked from commit e2d63e4c32)
2025-08-05 06:32:41 +00:00
ruthra kumar
a74b3e3602 Merge pull request #48939 from frappe/mergify/bp/version-15-hotfix/pr-48575
feat(payment gateway account): add company (backport #48575)
2025-08-05 10:43:12 +05:30
Ravibharathi
8259a748f6 Merge pull request #48575 from aerele/company-payment-gateway
feat(payment gateway account): add company

(cherry picked from commit 02380c3eab)
2025-08-05 10:12:39 +05:30
ruthra kumar
59c6e8f233 Merge pull request #48953 from frappe/mergify/bp/version-15-hotfix/pr-48951
chore: added now as default value for the posting time (backport #48951)
2025-08-05 10:08:55 +05:30
Rohit Waghchaure
dfa35363b9 chore: added now as default value for the posting time
(cherry picked from commit b3cebd87c8)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/pos_invoice.json
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
#	erpnext/stock/doctype/delivery_note/delivery_note.json
#	erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
2025-08-05 09:44:21 +05:30
mergify[bot]
bab2e86c01 chore: remove wrongly configured 'pos*' assignment from CODEOWNERS (backport #48954) (#48955)
chore: remove wrongly configured 'pos*' assignment from CODEOWNERS (#48954)

(cherry picked from commit cc26d5da14)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-08-05 00:10:09 +05:30
ruthra kumar
fc462d4526 Merge pull request #48937 from frappe/mergify/bp/version-15-hotfix/pr-48926
fix: provide missing `company` in report records that require reference to `Company:company:default_currency` (backport #48926)
2025-08-04 11:02:53 +05:30
Devin Slauenwhite
5b0486ca26 fix: payment ledger voucher seperator row currencies
(cherry picked from commit c03f1c25cf)
2025-08-04 05:00:08 +00:00
Devin Slauenwhite
dc4b236951 fix: provide company for outstanding record.
(cherry picked from commit 97959dbe75)
2025-08-04 05:00:08 +00:00
Devin Slauenwhite
070190d07b fix: provide missing company in report records that require reference to Company:company:default_currency
(cherry picked from commit 7f3905185c)
2025-08-04 05:00:08 +00:00
Devin Slauenwhite
03550066a7 Revert "fix: set proper currency format"
This reverts PR https://github.com/frappe/erpnext/pull/42458
This reverts commit 2533808f1e.

(cherry picked from commit 316470eee4)
2025-08-04 05:00:07 +00:00
ruthra kumar
b93cfc5d83 Merge pull request #48936 from frappe/mergify/bp/version-15-hotfix/pr-48136
fix(accounts):enable allow_on_submit for accounting dimensions in repost settings allowed doctypes (backport #48136)
2025-08-04 09:34:13 +05:30
ravibharathi656
ad56177234 fix: include child doctypes in allow_on_submit patch for accounting dimensions
(cherry picked from commit 1e37fd8991)
2025-08-04 09:14:13 +05:30
ravibharathi656
e22f93f1fb fix: enable allow_on_submit for accounting dimensions in allowed doctypes
(cherry picked from commit 55e79c4dfd)
2025-08-04 03:35:35 +00:00
ravibharathi656
56dca02cab fix: include child doctypes in repostable accounting types
(cherry picked from commit fbd8fd7d22)
2025-08-04 03:35:34 +00:00
Frappe PR Bot
8795ce975f chore(release): Bumped to Version 15.72.3
## [15.72.3](https://github.com/frappe/erpnext/compare/v15.72.2...v15.72.3) (2025-08-03)

### Performance Improvements

* process_gl_map causing performance issues in the reposting ([58f6534](58f6534d8b))
2025-08-03 10:42:14 +00:00
rohitwaghchaure
629cdd62f2 Merge pull request #48932 from frappe/mergify/bp/version-15/pr-48914
perf: process_gl_map causing performance issues in the reposting (backport #48914)
2025-08-03 16:08:38 +05:30
Rohit Waghchaure
58f6534d8b perf: process_gl_map causing performance issues in the reposting
(cherry picked from commit a96fa55704)
2025-08-03 09:46:12 +00:00
Mihir Kandoi
c7db277aa8 Merge pull request #48928 from frappe/mergify/bp/version-15-hotfix/pr-48748
fix: account currency validation to exclude cancelled entries (backport #48748)
2025-08-02 22:34:05 +05:30
Dev Dusija
4ce4d345e7 fix: account currency validation to exclude cancelled entries
(cherry picked from commit c9c45fe89f)
2025-08-02 16:48:31 +00:00
Mihir Kandoi
f714888d48 Merge pull request #48924 from frappe/mergify/bp/version-15-hotfix/pr-48915
fix: set use_serial_batch_fields when creating PR from PO (backport #48915)
2025-08-02 20:57:46 +05:30
Kavin
0207b82f82 fix: set use_serial_batch_fields when creating PR from PO
(cherry picked from commit a384c96617)
2025-08-02 15:12:26 +00:00
rohitwaghchaure
7f1dbeee8b Merge pull request #48914 from rohitwaghchaure/fixed-support-45378
perf: process_gl_map causing performance issues in the reposting
2025-08-02 19:01:40 +05:30
Rohit Waghchaure
a96fa55704 perf: process_gl_map causing performance issues in the reposting 2025-08-02 18:44:32 +05:30
Mihir Kandoi
4488b27fe3 Merge pull request #48910 from frappe/mergify/bp/version-15-hotfix/pr-48908
fix: include Sales Invoice in SABB validation for packed items (backport #48908)
2025-08-01 17:34:59 +05:30
Mihir Kandoi
d862a742b0 fix: include Sales Invoice in SABB validation for packed items
(cherry picked from commit 2ce297aff8)
2025-08-01 11:50:21 +00:00
Frappe PR Bot
31343b6287 chore(release): Bumped to Version 15.72.2
## [15.72.2](https://github.com/frappe/erpnext/compare/v15.72.1...v15.72.2) (2025-08-01)

### Bug Fixes

* multiple fixes for advance payment accounting (backport [#48341](https://github.com/frappe/erpnext/issues/48341)) ([#48896](https://github.com/frappe/erpnext/issues/48896)) ([52b9f92](52b9f92553))
2025-08-01 10:25:24 +00:00
ruthra kumar
e0850ed209 Merge pull request #48906 from frappe/mergify/bp/version-15/pr-48896
fix: multiple fixes for advance payment accounting (backport #48341) (backport #48896)
2025-08-01 15:53:57 +05:30
mergify[bot]
52b9f92553 fix: multiple fixes for advance payment accounting (backport #48341) (#48896)
* fix: multiple fixes for advance payment accounting

(cherry picked from commit e70caedddc)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
#	erpnext/accounts/doctype/payment_entry/payment_entry.py
#	erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
#	erpnext/accounts/utils.py
#	erpnext/controllers/accounts_controller.py
#	erpnext/patches/v15_0/create_advance_payment_ledger_records.py

* chore: resolve conflicts

* fix: do not execute patch if no advance doctypes

---------

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
Co-authored-by: ljain112 <ljain112@gmail.com>
(cherry picked from commit cb0addc122)
2025-08-01 10:01:31 +00:00
mergify[bot]
cb0addc122 fix: multiple fixes for advance payment accounting (backport #48341) (#48896)
* fix: multiple fixes for advance payment accounting

(cherry picked from commit e70caedddc)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
#	erpnext/accounts/doctype/payment_entry/payment_entry.py
#	erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
#	erpnext/accounts/utils.py
#	erpnext/controllers/accounts_controller.py
#	erpnext/patches/v15_0/create_advance_payment_ledger_records.py

* chore: resolve conflicts

* fix: do not execute patch if no advance doctypes

---------

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
Co-authored-by: ljain112 <ljain112@gmail.com>
2025-08-01 15:30:30 +05:30
mergify[bot]
5de5a8bfd5 fix: prevent negative values in BOM fields (#48520, #48662) (backport #48696) (#48897)
* fix: prevent negative values in BOM fields (#48520, #48662) (#48696)

* fix: prevent negative values in BOM fields (#48520, #48662)

* fix: applied non_negative validation using Desk UI for BOM fields

(cherry picked from commit 3a80e116e8)

# Conflicts:
#	erpnext/manufacturing/doctype/bom_operation/bom_operation.json
#	erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Vishist16 <101823906+Vishist16@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-07-31 15:42:23 +00:00
Frappe PR Bot
396886c6e8 chore(release): Bumped to Version 15.72.1
## [15.72.1](https://github.com/frappe/erpnext/compare/v15.72.0...v15.72.1) (2025-07-31)

### Bug Fixes

* failing subcontracting patch ([14d5c7f](14d5c7f3cd))
2025-07-31 11:05:20 +00:00
Mihir Kandoi
3a0db8d550 Merge pull request #48892 from frappe/mergify/bp/version-15/pr-48888
fix: failing subcontracting patch (backport #48887) (backport #48888)
2025-07-31 16:34:07 +05:30
Mihir Kandoi
9e661b206c chore: add date so patch can rerun
(cherry picked from commit ed927a2147)
2025-07-31 10:46:45 +00:00
Mihir Kandoi
14d5c7f3cd fix: failing subcontracting patch
(cherry picked from commit bb43419944)
(cherry picked from commit 2f4a9f283d)
2025-07-31 10:46:45 +00:00
Mihir Kandoi
5291c66ea1 Merge pull request #48888 from frappe/mergify/bp/version-15-hotfix/pr-48887
fix: failing subcontracting patch (backport #48887)
2025-07-31 16:15:15 +05:30
Mihir Kandoi
ed927a2147 chore: add date so patch can rerun 2025-07-31 16:00:35 +05:30
Mihir Kandoi
2f4a9f283d fix: failing subcontracting patch
(cherry picked from commit bb43419944)
2025-07-31 10:10:40 +00:00
Mihir Kandoi
79bfcbf424 Merge pull request #48885 from mihir-kandoi/version-15-hotfix
fix: permission error on tests
2025-07-31 14:52:34 +05:30
Mihir Kandoi
c9d69d9629 fix: use checkout@v2 instead of v4 2025-07-31 14:19:44 +05:30
Mihir Kandoi
6f12029477 fix: permission error on tests 2025-07-31 14:04:02 +05:30
mergify[bot]
7bb127de63 Merge pull request #48876 from frappe/mergify/bp/version-15-hotfix/pr-48873
fix: return conversion factor of variant and not template (backport #48873)
2025-07-31 07:03:15 +00:00
Diptanil Saha
87de70f4fd Merge pull request #48877 from frappe/mergify/bp/version-15-hotfix/pr-48619
fix(accounts): allow default bank account per company (backport #48619)
2025-07-31 12:21:04 +05:30
Khushi Rawat
782fecca46 Merge pull request #48857 from khushi8112/asset-depreciation-on-cancel-of-sales-invoice
fix: do not recalculate depreciation on sale invoice cancellation for fully depreciated asset
2025-07-31 12:11:02 +05:30
Nikhil Kothari
8cd90de70b fix(accounts): allow default bank account per company
(cherry picked from commit 982550b92c)
2025-07-31 06:34:31 +00:00
Diptanil Saha
0b9a8ee67c Merge pull request #48859 from frappe/mergify/bp/version-15-hotfix/pr-48858
fix: server error on opportunity summary by sales stage report (backport #48858)
2025-07-30 16:04:47 +05:30
diptanilsaha
652589f636 fix: server error on opportunity summary by sales stage report
(cherry picked from commit 830b3ba1e5)
2025-07-30 10:20:05 +00:00
khushi8112
106f7ea112 test: test asset status after sales invoice creation and cancellation 2025-07-30 13:51:34 +05:30
khushi8112
db41b14317 fix: do not recalculate depreciation on sale invoice cancellation for fully depreciated asset 2025-07-30 13:50:32 +05:30
Mihir Kandoi
609191c3a5 Merge pull request #48852 from frappe/mergify/bp/version-15-hotfix/pr-48851 2025-07-29 22:37:58 +05:30
Mihir Kandoi
7489c9159d chore: resolve conflicts 2025-07-29 22:22:25 +05:30
Mihir Kandoi
b48619078d Update CODEOWNERS
(cherry picked from commit b9b3302b69)

# Conflicts:
#	CODEOWNERS
2025-07-29 16:49:03 +00:00
Diptanil Saha
523007588d Merge pull request #48850 from frappe/mergify/bp/version-15-hotfix/pr-48839
fix: change modified timestamp so migrations work (backport #48839)
2025-07-29 21:18:48 +05:30
Frappe PR Bot
b4a6d09d5d chore(release): Bumped to Version 15.72.0
# [15.72.0](https://github.com/frappe/erpnext/compare/v15.71.1...v15.72.0) (2025-07-29)

### Bug Fixes

* append finance book row only when calculate depreciation is checked ([36f22f9](36f22f929d))
* attribute error in payment entry ([3739e2c](3739e2ca5a))
* avoid auto_repeat on duplicate ([65a2706](65a27066cc))
* correct query filter assignment in stock ledger and balance reports ([fa01bdc](fa01bdc490))
* create job card for selected operations only ([23180da](23180dad42))
* do not set value after depreciation as zero ([20bbfc5](20bbfc504f))
* enhance warehouse filter to support list and tuple values ([#48755](https://github.com/frappe/erpnext/issues/48755)) ([0cb2c41](0cb2c41cba))
* error when trying to edit quantity of top most FG in bom creator ([bd7de51](bd7de515b1))
* fetch item valuation rate for internal transactions ([b23f7a9](b23f7a9d91))
* fetch payment term template from order ([ee8eb36](ee8eb368e7))
* get default company currency ([622052b](622052b950))
* handle empty warehouse condition in get_warehouse_condition function; typo; ([1d52a8f](1d52a8fb69))
* ignore is overridden by transaction.js upon clicking cancel ([424baed](424baed077))
* include empty values in user permission ([cfcd21d](cfcd21d5c6))
* incorrect GL entries ([207d2ac](207d2ac63c))
* Misclassification of Journal Voucher Entries in Customer Ledger Summary ([#48041](https://github.com/frappe/erpnext/issues/48041)) ([01fcd98](01fcd98c84))
* over billed purchase receipt status ([1efdff0](1efdff0ad1))
* patch to enable fetch_valuation_rate_for_internal_transaction ([bf5b6a5](bf5b6a540f))
* patch to set default buying price list in material request ([#48680](https://github.com/frappe/erpnext/issues/48680)) ([fd1c213](fd1c213a8d))
* **pick list:** make warehouse editable ([6a50410](6a5041042e))
* post gl entry on completion date of asset repair ([5a82b72](5a82b723c2))
* prevent concurrency issues ([ad75754](ad75754ca6))
* prevent negative scrap quantity in Job Card ([#48545](https://github.com/frappe/erpnext/issues/48545)) ([ae945b2](ae945b2e6f))
* remove alias for order by field ([193fbcb](193fbcba11))
* **sales-order:** disallow address edits after sales order is submitted ([2073e98](2073e98613))
* serial no warehouse for backdated stock reco ([b82aea4](b82aea4a87))
* set company as mandatory ([49befc1](49befc1dfd))
* set letter head from company if exists ([d4fae00](d4fae00b80))
* sql error in quality inspection ([3e92ae8](3e92ae8bd0))
* status in MR (material transfer) when using transit stock entries ([584b442](584b442824))
* test case ([9fe1e6d](9fe1e6d0bd))
* test case ([c7dcbed](c7dcbed16f))
* **test:** update tests ([62033b5](62033b5c7a))
* update advance paid amount on unreconcile ([074a706](074a7065be))
* update asset value after revaluation cancellation ([d9b24a3](d9b24a30eb))
* update get_data function to use item_query ([9bf0d85](9bf0d852ee))
* update subscription details patch ([ebda396](ebda396518))
* use db_set in email_campaign (backport [#45679](https://github.com/frappe/erpnext/issues/45679)) ([#48806](https://github.com/frappe/erpnext/issues/48806)) ([9b59fb6](9b59fb659b))
* use the item_query for get_data ([a7e8f40](a7e8f404f7))
* valuation for rejected materials ([d378e51](d378e51492))
* warehouse filter query by chaining conditions ([b57163b](b57163b7be))

### Features

* add fetch_valuation_rate_for_internal_transaction in accounts settings ([f8d1e5a](f8d1e5a0d3))
* Add non-negative constraint to workstation cost fields (backport [#48557](https://github.com/frappe/erpnext/issues/48557)) ([#48826](https://github.com/frappe/erpnext/issues/48826)) ([e1d7ec9](e1d7ec906f))
* enhance apply_warehouse_filter to support multiple warehouses in filters ([801cda3](801cda3813))
* option to recalculate costing and billing fields in project ([6adc8a0](6adc8a09c0))
* show opening/closing balance in cash flow report ([#47877](https://github.com/frappe/erpnext/issues/47877)) ([7fd5b2b](7fd5b2b26a))
* update stock balance report to support multi-select for items and warehouses ([2b08c5b](2b08c5b769))
* update stock ledger report to support multi-select for warehouses and items ([ecf9e6e](ecf9e6e748))
2025-07-29 15:39:56 +00:00
ruthra kumar
943aca9739 Merge pull request #48832 from frappe/version-15-hotfix
chore: release v15
2025-07-29 21:08:37 +05:30
Diptanil Saha
184cabdcb0 chore: resolve conflict 2025-07-29 21:02:04 +05:30
Ayush Chaudhari
f8ea431551 fix: change modified timestamp so migrations work
(cherry picked from commit c18d565d3e)

# Conflicts:
#	erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json
2025-07-29 15:30:28 +00:00
ruthra kumar
9732973b73 Merge pull request #48849 from frappe/mergify/bp/version-15-hotfix/pr-48534
fix: update advance paid amount on unreconcile (backport #48534)
2025-07-29 20:52:07 +05:30
Ravibharathi
074a7065be fix: update advance paid amount on unreconcile
(cherry picked from commit 99f7eb38d3)
2025-07-29 15:06:20 +00:00
ruthra kumar
9f36531f2a Merge pull request #48847 from frappe/mergify/bp/version-15-hotfix/pr-48835
fix: include empty values in user permission (backport #48835)
2025-07-29 20:17:23 +05:30
l0gesh29
cfcd21d5c6 fix: include empty values in user permission
(cherry picked from commit f13d98fc7c)
2025-07-29 14:26:35 +00:00
rohitwaghchaure
5c34299b1e Merge pull request #48843 from frappe/mergify/bp/version-15-hotfix/pr-48743
fix(pick list): make warehouse editable (backport #48743)
2025-07-29 18:42:43 +05:30
rohitwaghchaure
4fda27fdba Merge pull request #48842 from frappe/mergify/bp/version-15-hotfix/pr-48841
fix: serial no warehouse for backdated stock reco (backport #48841)
2025-07-29 18:32:45 +05:30
ravibharathi656
6a5041042e fix(pick list): make warehouse editable
(cherry picked from commit f5beda48dc)
2025-07-29 12:55:17 +00:00
Rohit Waghchaure
b82aea4a87 fix: serial no warehouse for backdated stock reco
(cherry picked from commit 1deedc766c)
2025-07-29 12:46:52 +00:00
Khushi Rawat
21bdf6ef14 Merge pull request #48840 from khushi8112/finance-books-issue-when-asset-splitting
fix: finance books issue when asset splitting
2025-07-29 18:01:37 +05:30
khushi8112
bc1d3ea017 chore: remove print statement 2025-07-29 17:43:21 +05:30
khushi8112
1be071683a test: test assets after split 2025-07-29 17:39:48 +05:30
khushi8112
36f22f929d fix: append finance book row only when calculate depreciation is checked 2025-07-29 17:39:04 +05:30
ruthra kumar
1d85da43a6 Merge pull request #48838 from frappe/mergify/bp/version-15-hotfix/pr-48837
fix: unable to cancel PO if unreconciliation is done (backport #48837)
2025-07-29 17:17:35 +05:30
ruthra kumar
424baed077 fix: ignore is overridden by transaction.js upon clicking cancel
which overrides with 'Serial and Batch Bundle'

(cherry picked from commit cf70147c0d)
2025-07-29 11:34:31 +00:00
ruthra kumar
9508ae5044 Merge pull request #48836 from frappe/mergify/bp/version-15-hotfix/pr-48782
fix: attribute error in payment entry (backport #48782)
2025-07-29 17:03:55 +05:30
ljain112
1697ac0b57 chore: added test case for reconciliation_effect_date
(cherry picked from commit f7ee9ee967)
2025-07-29 11:13:12 +00:00
ljain112
3739e2ca5a fix: attribute error in payment entry
(cherry picked from commit dc841fe661)
2025-07-29 11:13:12 +00:00
Khushi Rawat
ec9747cc99 Merge pull request #48833 from khushi8112/bp-v15-pr-48649
fix: post gl entry on completion date of asset repair
2025-07-29 15:36:43 +05:30
Khushi Rawat
21f5541158 Merge pull request #48629 from khushi8112/update-value-after-depreciation-after-revaluation
fix: update asset value after revaluation cancellation
2025-07-29 15:33:33 +05:30
khushi8112
5a82b723c2 fix: post gl entry on completion date of asset repair 2025-07-29 15:17:30 +05:30
Mihir Kandoi
d7a9c7b161 Merge pull request #48828 from frappe/mergify/bp/version-15-hotfix/pr-48700
fix: prevent negative scrap quantity in Job Card (#48545) (backport #48700)
2025-07-29 14:26:05 +05:30
mergify[bot]
e1d7ec906f feat: Add non-negative constraint to workstation cost fields (backport #48557) (#48826)
* feat: Add non-negative constraint to workstation cost fields

(cherry picked from commit a2bb557570)

# Conflicts:
#	erpnext/manufacturing/doctype/workstation/workstation.json

* fix: Add non-negative constraint to job capacity field in workstation

(cherry picked from commit 92a12d7fea)

# Conflicts:
#	erpnext/manufacturing/doctype/workstation/workstation.json

* chore: resolve conflicts

---------

Co-authored-by: KerollesFathy <kerolles.f@outlook.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-07-29 08:53:25 +00:00
ruthra kumar
fe1292e3fa Merge pull request #48829 from frappe/mergify/bp/version-15-hotfix/pr-48593
fix(sales-order): disallow address edits after sales order is submitted (backport #48593)
2025-07-29 14:19:38 +05:30
Diptanil Saha
61e5c52419 Merge pull request #48830 from frappe/mergify/bp/version-15-hotfix/pr-48769
fix: set letter head from company if exists (backport #48769)
2025-07-29 13:56:33 +05:30
ravibharathi656
d4fae00b80 fix: set letter head from company if exists
(cherry picked from commit d163da171f)
2025-07-29 08:23:11 +00:00
Bhavan23
2073e98613 fix(sales-order): disallow address edits after sales order is submitted
(cherry picked from commit daac7c589b)
2025-07-29 08:10:07 +00:00
Vishist Singh Solanki
ae945b2e6f fix: prevent negative scrap quantity in Job Card (#48545)
(cherry picked from commit 94ec76545c)
2025-07-29 07:57:54 +00:00
ruthra kumar
84c0321785 Merge pull request #48827 from frappe/mergify/bp/version-15-hotfix/pr-47877
feat(cashflow): show opening/closing balance (backport #47877)
2025-07-29 13:25:18 +05:30
Mihir Kandoi
f0f8666baa Merge pull request #48682 from frappe/mergify/bp/version-15-hotfix/pr-48680
fix: patch to set default buying price list in material request (backport #48680)
2025-07-29 13:23:16 +05:30
Mihir Kandoi
5d7b8200fa Merge pull request #48681 from frappe/mergify/bp/version-15-hotfix/pr-48653
feat: button to recalculate costing and billing fields in project (backport #48653)
2025-07-29 13:22:59 +05:30
Logesh Periyasamy
7fd5b2b26a feat: show opening/closing balance in cash flow report (#47877)
* feat: add checkbox to carryforward opening balance

* fix: ignore period closing voucher

* chore: rename filter check box

* feat: add total for opening and closing balance

* fix: update section name

* fix: remove section rename

---------

Co-authored-by: venkat102 <venkatesharunachalam659@gmail.com>
(cherry picked from commit 88b9f8d68c)
2025-07-29 07:35:55 +00:00
Mihir Kandoi
85f1efe67e Merge pull request #48777 from frappe/mergify/bp/version-15-hotfix/pr-48773
fix: create job card for selected operations only (backport #48773)
2025-07-29 13:00:28 +05:30
ruthra kumar
d543a0c959 Merge pull request #48823 from frappe/mergify/bp/version-15-hotfix/pr-48757
fix: add patch for update subscription details (backport #48757)
2025-07-29 12:29:51 +05:30
l0gesh29
ebda396518 fix: update subscription details patch
(cherry picked from commit c7b1379a7f)
2025-07-29 06:38:09 +00:00
Mihir Kandoi
c086f43964 Merge pull request #48821 from frappe/mergify/bp/version-15-hotfix/pr-48820
fix: over billed purchase receipt status (backport #48820)
2025-07-29 12:03:28 +05:30
Mihir Kandoi
3336b5d55d Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-48820 2025-07-29 11:48:27 +05:30
mergify[bot]
18b007dbf2 Merge pull request #48805 from frappe/mergify/bp/version-15-hotfix/pr-48676
fix: missing account in GL entries (subcontracting) (backport #48676)
2025-07-29 11:48:12 +05:30
Mihir Kandoi
1efdff0ad1 fix: over billed purchase receipt status
(cherry picked from commit 15e354f76e)
2025-07-29 06:02:50 +00:00
Mihir Kandoi
92e315c4e1 Merge pull request #48811 from frappe/mergify/bp/version-15-hotfix/pr-48804
fix: sql error in quality inspection (backport #48804)
2025-07-29 11:18:46 +05:30
Mihir Kandoi
22e6ea41f7 Merge pull request #48819 from frappe/mergify/bp/version-15-hotfix/pr-39555
ci: Add fake passing tests when CI is skipped (backport #39555)
2025-07-29 11:18:02 +05:30
ruthra kumar
1c81b2c684 Merge pull request #48815 from frappe/mergify/bp/version-15-hotfix/pr-48797
fix: avoid auto_repeat on duplicate (backport #48797)
2025-07-29 09:18:28 +05:30
ruthra kumar
907358a356 Merge pull request #48814 from frappe/mergify/bp/version-15-hotfix/pr-48796
fix: remove alias for order by field (backport #48796)
2025-07-29 09:17:55 +05:30
Mihir Kandoi
9c3011cb9f Potential fix for code scanning alert no. 9: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-07-28 22:23:42 +05:30
Mihir Kandoi
5840a242a6 Potential fix for code scanning alert no. 13: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-07-28 22:23:34 +05:30
Ankush Menat
4fd32fc3bd ci: Add fake passing tests when CI is skipped (#39555)
(cherry picked from commit dfda5ad673)
2025-07-28 16:50:44 +00:00
Diptanil Saha
2d4419c9d5 Merge pull request #48818 from frappe/mergify/bp/version-15-hotfix/pr-48778
fix: fetch payment term template from order (backport #48778)
2025-07-28 20:41:42 +05:30
ravibharathi656
ee8eb368e7 fix: fetch payment term template from order
(cherry picked from commit 5ed34d6ff9)
2025-07-28 14:55:42 +00:00
Diptanil Saha
95470b4262 Merge pull request #48816 from diptanilsaha/backport_48675
feat(internal-transaction): fetch valuation rate for internal transaction (backport #48675, #48794)
2025-07-28 18:45:30 +05:30
diptanilsaha
bf5b6a540f fix: patch to enable fetch_valuation_rate_for_internal_transaction 2025-07-28 18:25:16 +05:30
mithili
65a27066cc fix: avoid auto_repeat on duplicate
(cherry picked from commit 2c54f49cbc)
2025-07-28 12:29:13 +00:00
rohitwaghchaure
f772976676 Merge pull request #48809 from frappe/mergify/bp/version-15-hotfix/pr-48787
fix: concurrency issues (backport #48787)
2025-07-28 17:55:49 +05:30
rohitwaghchaure
5e6f957e97 Merge pull request #48808 from frappe/mergify/bp/version-15-hotfix/pr-48801
fix: incorrect GL entries (backport #48801)
2025-07-28 17:55:09 +05:30
diptanilsaha
b23f7a9d91 fix: fetch item valuation rate for internal transactions 2025-07-28 17:54:56 +05:30
l0gesh29
f8d1e5a0d3 feat: add fetch_valuation_rate_for_internal_transaction in accounts settings 2025-07-28 17:54:32 +05:30
l0gesh29
1ca81887ca chore: rename variable
(cherry picked from commit 8fdda31e45)
2025-07-28 12:10:10 +00:00
l0gesh29
193fbcba11 fix: remove alias for order by field
(cherry picked from commit 048b87328b)
2025-07-28 12:10:10 +00:00
ruthra kumar
794fac12a4 Merge pull request #48812 from frappe/mergify/bp/version-15-hotfix/pr-48480
Currency sales partner commision report (backport #48480)
2025-07-28 17:29:32 +05:30
mergify[bot]
9b59fb659b fix: use db_set in email_campaign (backport #45679) (#48806)
Bug fix in email_campaign's update_status function. (#45679)

During the scheduler event of set_email_campaign_status, the function calling update_status isn't saving the modified status field.

(cherry picked from commit 88e68bb803)

Co-authored-by: harshpwctech <84438948+harshpwctech@users.noreply.github.com>
2025-07-28 17:26:17 +05:30
mithili
8d25269de6 refactor: remove join in sql
(cherry picked from commit 9638151f9d)
2025-07-28 11:42:54 +00:00
mithili
8314059bf7 chore: update query to fetch company currency
(cherry picked from commit 998617879c)
2025-07-28 11:42:53 +00:00
mithili
622052b950 fix: get default company currency
(cherry picked from commit 984947f333)
2025-07-28 11:42:53 +00:00
mithili
49befc1dfd fix: set company as mandatory
(cherry picked from commit 2de2ea9f58)
2025-07-28 11:42:53 +00:00
Mihir Kandoi
3e92ae8bd0 fix: sql error in quality inspection
(cherry picked from commit 062b245e3f)
2025-07-28 11:24:29 +00:00
Rohit Waghchaure
ad75754ca6 fix: prevent concurrency issues
(cherry picked from commit a186b1266d)
2025-07-28 10:59:17 +00:00
Rohit Waghchaure
207d2ac63c fix: incorrect GL entries
(cherry picked from commit 4c273fcc99)
2025-07-28 10:59:06 +00:00
Mihir Kandoi
442fede0fe Merge pull request #48803 from frappe/mergify/bp/version-15-hotfix/pr-48793
fix: status in MR (material transfer) when using transit stock entries (backport #48793)
2025-07-28 16:04:11 +05:30
Mihir Kandoi
584b442824 fix: status in MR (material transfer) when using transit stock entries
(cherry picked from commit baa612bc72)
2025-07-28 10:03:28 +00:00
Mihir Kandoi
faa9006072 Merge pull request #48789 from mihir-kandoi/st44460
fix: error when trying to edit quantity of top most FG in bom creator
2025-07-28 14:53:19 +05:30
Assem Bahnasy
01fcd98c84 fix: Misclassification of Journal Voucher Entries in Customer Ledger Summary (#48041)
* fix: miscalculation of Invoiced Amount, Paid Amount, and Credit Amount in Customer Ledger Summary

* style: Apply ruff-format to customer_ledger_summary.py and ignore .venv/

* fix: Ensure .venv/ is ignored in .gitignore

* chore: removing backportrc line

* test: adding test_journal_voucher_against_return_invoice()

* fix: fixed test_journal_voucher_against_return_invoice function

* Revert .gitignore changes

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-07-28 14:35:59 +05:30
Mihir Kandoi
bd7de515b1 fix: error when trying to edit quantity of top most FG in bom creator 2025-07-28 11:42:13 +05:30
Smit Vora
8d8d47b0d6 Merge pull request #48729 from frappe/mergify/bp/version-15-hotfix/pr-48382 2025-07-26 12:13:27 +05:30
Soni Karm
0cb2c41cba fix: enhance warehouse filter to support list and tuple values (#48755) 2025-07-26 11:32:23 +05:30
Shreyas Sojitra
23180dad42 fix: create job card for selected operations only
(cherry picked from commit 27e5344188)
2025-07-25 04:36:33 +00:00
Liuyang
284e45011e Fix: Procurement Tracker - No permission to read Employee #48765 2025-07-24 16:53:07 +08:00
Frappe PR Bot
ad45063c76 chore(release): Bumped to Version 15.71.1
## [15.71.1](https://github.com/frappe/erpnext/compare/v15.71.0...v15.71.1) (2025-07-24)

### Bug Fixes

* call hooks after gle & sle rename ([#48706](https://github.com/frappe/erpnext/issues/48706)) ([3d7185f](3d7185fad7))
2025-07-24 01:39:52 +00:00
ruthra kumar
c3188ff9a3 Merge pull request #48762 from frappe/mergify/bp/version-15/pr-48706
fix: call hooks on gle and sle rename (backport #48706)
2025-07-24 07:08:24 +05:30
Kitti U. @ Ecosoft
3d7185fad7 fix: call hooks after gle & sle rename (#48706)
(cherry picked from commit ed79adebc4)
2025-07-24 06:34:51 +05:30
mergify[bot]
fd441e1eff refactor: call hooks after gle & sle rename (backport #48706) (#48754)
refactor: call hooks after gle & sle rename (#48706)

(cherry picked from commit ed79adebc4)

Co-authored-by: Kitti U. @ Ecosoft <kittiu@gmail.com>
2025-07-23 18:37:14 +05:30
rohitwaghchaure
0588b68cec Merge pull request #48734 from frappe/mergify/bp/version-15-hotfix/pr-48720
fix: valuation for rejected materials (backport #48720)
2025-07-23 08:45:35 +05:30
rohitwaghchaure
c2140625f5 chore: fixed test case 2025-07-23 08:27:33 +05:30
Frappe PR Bot
32029f4dca chore(release): Bumped to Version 15.71.0
# [15.71.0](https://github.com/frappe/erpnext/compare/v15.70.2...v15.71.0) (2025-07-23)

### Bug Fixes

* add alias for order by field ([4bef3cc](4bef3cc92f))
* add validation for account key ([90fa7db](90fa7db13c))
* added serial no condition ([467fe1d](467fe1d72f))
* carry forward the delivered_by_supplier check to PO ([6fddf4c](6fddf4c5aa))
* do not consider cancelled SLEs in report ([32915cf](32915cf2b7))
* fetch sales invoice based on mode_of_payment in item-wise sales register ([d04c256](d04c256b73))
* job card linter error (backport [#47561](https://github.com/frappe/erpnext/issues/47561)) ([#48695](https://github.com/frappe/erpnext/issues/48695)) ([a139cd4](a139cd4b5e))
* performance issue while submitting the purchase invoice ([b9e6f52](b9e6f524e5))
* **period closing voucher:** closing account head debit and debit in account currency should be equal ([98bd880](98bd880c73))
* pos customer selection on new order ([#48623](https://github.com/frappe/erpnext/issues/48623)) ([a46cafe](a46cafe652))
* precision issue for Sales Incoming Rate ([3e53660](3e53660bba))
* **production plan:** add company filter to sub assembly warehouse ([e683703](e68370359f))
* resolve bundle item into line item if againt default supplier checked ([725f9ea](725f9ea012))
* resolve sql syntax on accounting dimension ([96a1444](96a1444e92))
* sales partner in pos invoice ([#48670](https://github.com/frappe/erpnext/issues/48670)) ([65efc7e](65efc7e950)), closes [#48667](https://github.com/frappe/erpnext/issues/48667) [#48669](https://github.com/frappe/erpnext/issues/48669)
* set delivery date if missing ([8f23ca5](8f23ca5c6b))
* show amount for exchange gain or loss account ([38b223e](38b223e732))
* stand-alone credit note gl entries ([93c2a67](93c2a67930))
* **transaction:** recalculate tax and total when quantity changes (backport [#48565](https://github.com/frappe/erpnext/issues/48565)) ([#48625](https://github.com/frappe/erpnext/issues/48625)) ([2b1fdba](2b1fdba7fd))
* update outstanding amount on payment reconcillation ([0d496bb](0d496bb05f))
* view ledger button of company on chart of accounts (backport [#48677](https://github.com/frappe/erpnext/issues/48677)) ([#48678](https://github.com/frappe/erpnext/issues/48678)) ([56f5ec9](56f5ec961f))

### Features

* consider process less when calculating pending qty in work order ([2b42848](2b42848376))

### Reverts

* do not set pay_to_recd_from to None ([ab79e5d](ab79e5d946))
2025-07-23 02:54:26 +00:00
ruthra kumar
f25aff7b97 Merge pull request #48745 from frappe/version-15-hotfix
chore: release v15
2025-07-23 08:23:03 +05:30
ruthra kumar
3825490f0d Merge pull request #48752 from frappe/mergify/bp/version-15-hotfix/pr-48650
fix: update outstanding amount on payment reconcillation (backport #48650)
2025-07-23 07:24:53 +05:30
ruthra kumar
0a1b815546 Merge pull request #48751 from frappe/mergify/bp/version-15-hotfix/pr-48732
fix: resolve sql syntax on accounting dimension (backport #48732)
2025-07-23 07:24:18 +05:30
ruthra kumar
3a94c6c86c Merge pull request #48747 from frappe/mergify/bp/version-15-hotfix/pr-48733
fix: add alias for order by field (backport #48733)
2025-07-23 06:53:15 +05:30
ravibharathi656
0d496bb05f fix: update outstanding amount on payment reconcillation
(cherry picked from commit 478766c600)
2025-07-23 01:23:13 +00:00
l0gesh29
96a1444e92 fix: resolve sql syntax on accounting dimension
(cherry picked from commit 1662b7c311)
2025-07-23 01:15:13 +00:00
ruthra kumar
f9d8f510a0 Merge pull request #48725 from frappe/mergify/bp/version-15-hotfix/pr-48671
revert: do not set pay_to_recd_from to None (backport #48671)
2025-07-22 17:57:46 +05:30
ruthra kumar
35bc733d28 Merge pull request #48726 from frappe/mergify/bp/version-15-hotfix/pr-48690
fix: set delivery date if missing (backport #48690)
2025-07-22 17:56:10 +05:30
l0gesh29
4bef3cc92f fix: add alias for order by field
(cherry picked from commit feaf39a812)
2025-07-22 12:25:17 +00:00
Smit Vora
176a124f1a chore: restore removed import as it's used in v15 2025-07-22 15:50:18 +05:30
ruthra kumar
d95306fd60 Merge pull request #48741 from frappe/mergify/bp/version-15-hotfix/pr-48665
fix: show amount for exchange gain or loss account (backport #48665)
2025-07-22 12:55:57 +05:30
l0gesh29
90fa7db13c fix: add validation for account key
(cherry picked from commit b6da350c20)
2025-07-22 06:44:06 +00:00
l0gesh29
38b223e732 fix: show amount for exchange gain or loss account
(cherry picked from commit 4f90f50eb2)
2025-07-22 06:44:06 +00:00
rohitwaghchaure
4453e447dc chore: fix conflicts 2025-07-22 09:57:07 +05:30
Rohit Waghchaure
d378e51492 fix: valuation for rejected materials
(cherry picked from commit b7039cc506)

# Conflicts:
#	erpnext/controllers/buying_controller.py
2025-07-22 04:18:22 +00:00
rohitwaghchaure
526c1e7c9a Merge pull request #48727 from frappe/mergify/bp/version-15-hotfix/pr-48704
fix(production plan): add company filter to sub assembly warehouse (backport #48704)
2025-07-21 20:04:42 +05:30
Karm Soni
9bf0d852ee fix: update get_data function to use item_query
(cherry picked from commit 8a97b39028)
2025-07-21 14:33:53 +00:00
Karm Soni
fb81202830 refactor: revert indentation
(cherry picked from commit 063c4e9720)
2025-07-21 14:33:52 +00:00
Karm Soni
a1aee44014 refactor: remove unused imports in stock_balance.py
(cherry picked from commit bc46045cc7)
2025-07-21 14:33:52 +00:00
Karm Soni
b57163b7be fix: warehouse filter query by chaining conditions
(cherry picked from commit 7a266113ed)
2025-07-21 14:33:52 +00:00
Karm Soni
1d52a8fb69 fix: handle empty warehouse condition in get_warehouse_condition function; typo;
(cherry picked from commit fca9843fc2)
2025-07-21 14:33:52 +00:00
Karm Soni
a7e8f404f7 fix: use the item_query for get_data
(cherry picked from commit 169caaf66f)
2025-07-21 14:33:51 +00:00
Karm Soni
fa01bdc490 fix: correct query filter assignment in stock ledger and balance reports
(cherry picked from commit e60c711fdc)
2025-07-21 14:33:51 +00:00
Karm Soni
62033b5c7a fix(test): update tests
(cherry picked from commit 0a71ca6739)
2025-07-21 14:33:51 +00:00
Karm Soni
ecf9e6e748 feat: update stock ledger report to support multi-select for warehouses and items
(cherry picked from commit f2afd98725)
2025-07-21 14:33:50 +00:00
Karm Soni
72e8ce0449 refactor: use existing functionality
(cherry picked from commit 2882576479)
2025-07-21 14:33:50 +00:00
Karm Soni
801cda3813 feat: enhance apply_warehouse_filter to support multiple warehouses in filters
(cherry picked from commit 2ff1dcc391)
2025-07-21 14:33:50 +00:00
Karm Soni
2b08c5b769 feat: update stock balance report to support multi-select for items and warehouses
(cherry picked from commit 0d2a88bafc)
2025-07-21 14:33:49 +00:00
ravibharathi656
e68370359f fix(production plan): add company filter to sub assembly warehouse
(cherry picked from commit 1728a95111)
2025-07-21 14:27:40 +00:00
ravibharathi656
8f23ca5c6b fix: set delivery date if missing
(cherry picked from commit cf6913891a)

# Conflicts:
#	erpnext/selling/doctype/sales_order/sales_order.py
2025-07-21 19:51:02 +05:30
ravibharathi656
e3ccdbb20b test: add test for pay_to_recd_from
(cherry picked from commit 7e12332ea5)
2025-07-21 14:10:08 +00:00
ravibharathi656
ab79e5d946 revert: do not set pay_to_recd_from to None
(cherry picked from commit 03d6550db3)
2025-07-21 14:10:08 +00:00
Mihir Kandoi
b607448fd8 Merge pull request #48699 from frappe/mergify/bp/version-15-hotfix/pr-48695 2025-07-18 21:50:00 +05:30
mergify[bot]
a139cd4b5e fix: job card linter error (backport #47561) (#48695)
* fix: job card linter error (#47561)

(cherry picked from commit 4174269091)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/work_order.py

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
(cherry picked from commit 4794e7acff)
2025-07-18 16:01:34 +00:00
Mihir Kandoi
8e91e6ac2c Merge pull request #48698 from frappe/revert-48695-mergify/bp/version-15-hotfix/pr-47561
Revert "fix: job card linter error (backport #47561)"
2025-07-18 21:27:35 +05:30
Mihir Kandoi
9501149bd8 Revert "fix: job card linter error (backport #47561) (#48695)"
This reverts commit 4794e7acff.
2025-07-18 21:26:28 +05:30
mergify[bot]
4794e7acff fix: job card linter error (backport #47561) (#48695)
* fix: job card linter error (#47561)

(cherry picked from commit 4174269091)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/work_order.py

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-07-18 21:26:21 +05:30
Mihir Kandoi
fd1c213a8d fix: patch to set default buying price list in material request (#48680)
* fix: patch to set default buying price list in material request

(cherry picked from commit 446264e496)
2025-07-18 11:14:15 +00:00
Mihir Kandoi
36a9f3b3e9 chore: rename recalculating to updating
(cherry picked from commit f6e16c1180)
2025-07-18 10:56:08 +00:00
Mihir Kandoi
6adc8a09c0 feat: option to recalculate costing and billing fields in project
(cherry picked from commit dd23d4c81b)
2025-07-18 10:56:07 +00:00
Mihir Kandoi
969c3a2b4d Merge pull request #48679 from frappe/mergify/bp/version-15-hotfix/pr-48631
feat: consider process less when calculating pending qty in work order (backport #48631)
2025-07-18 16:04:14 +05:30
Mihir Kandoi
2b42848376 feat: consider process less when calculating pending qty in work order
(cherry picked from commit 74c4ca68e5)
2025-07-18 10:24:04 +00:00
mergify[bot]
56f5ec961f fix: view ledger button of company on chart of accounts (backport #48677) (#48678)
fix: view ledger button of company on chart of accounts

(cherry picked from commit 98eb115746)

Co-authored-by: diptanilsaha <diptanil@frappe.io>
2025-07-18 15:48:39 +05:30
Diptanil Saha
65efc7e950 fix: sales partner in pos invoice (#48670)
* Merge pull request #48667 from diptanilsaha/fix_pos_sales_partner

fix: sales partner on pos invoice

* fix: remove incorrect report conditions and unset sales partner on consolidated sales invoice (#48669)

* fix: undo query changes for sales partner related reports

* fix: patch to remove sales partner from consolidated sales invoice
2025-07-18 11:09:32 +05:30
Frappe PR Bot
196c3fc656 chore(release): Bumped to Version 15.70.2
## [15.70.2](https://github.com/frappe/erpnext/compare/v15.70.1...v15.70.2) (2025-07-17)

### Bug Fixes

* stand-alone credit note gl entries ([84cf5ad](84cf5ad601))
2025-07-17 11:26:04 +00:00
rohitwaghchaure
1759e02c35 Merge pull request #48661 from frappe/mergify/bp/version-15/pr-48652
fix: stand-alone credit note gl entries (backport #48616) (backport #48652)
2025-07-17 16:54:50 +05:30
rohitwaghchaure
b83535bc31 Merge pull request #48658 from frappe/mergify/bp/version-15-hotfix/pr-48655
fix: precision issue for Sales Incoming Rate (backport #48655)
2025-07-17 16:23:35 +05:30
Rohit Waghchaure
84cf5ad601 fix: stand-alone credit note gl entries
(cherry picked from commit f3d6a64156)
(cherry picked from commit 93c2a67930)
2025-07-17 10:45:16 +00:00
Rohit Waghchaure
3e53660bba fix: precision issue for Sales Incoming Rate
(cherry picked from commit 7b99275ceb)
2025-07-17 10:05:43 +00:00
rohitwaghchaure
904cecfa91 Merge pull request #48652 from frappe/mergify/bp/version-15-hotfix/pr-48616
fix: stand-alone credit note gl entries (backport #48616)
2025-07-17 15:34:14 +05:30
Rohit Waghchaure
93c2a67930 fix: stand-alone credit note gl entries
(cherry picked from commit f3d6a64156)
2025-07-17 08:29:20 +00:00
Mihir Kandoi
179444f51e Merge pull request #48646 from frappe/mergify/bp/version-15-hotfix/pr-48645
fix: do not consider cancelled SLEs in report (backport #48645)
2025-07-17 12:01:31 +05:30
Mihir Kandoi
32915cf2b7 fix: do not consider cancelled SLEs in report
(cherry picked from commit 71578cb2ef)
2025-07-17 06:09:41 +00:00
Frappe PR Bot
83ab16b161 chore(release): Bumped to Version 15.70.1
## [15.70.1](https://github.com/frappe/erpnext/compare/v15.70.0...v15.70.1) (2025-07-16)

### Bug Fixes

* **period closing voucher:** closing account head debit and debit in account currency should be equal ([c554e2c](c554e2cce7))
2025-07-16 15:12:00 +00:00
ruthra kumar
8e16da08d0 Merge pull request #48641 from frappe/mergify/bp/version-15/pr-48612
fix(period closing voucher): closing account head debit and debit in account currency should be equal (backport #48612)
2025-07-16 20:40:46 +05:30
ruthra kumar
e8be903ed9 Merge pull request #48639 from frappe/mergify/bp/version-15-hotfix/pr-48612
fix(period closing voucher): closing account head debit and debit in account currency should be equal (backport #48612)
2025-07-16 20:31:21 +05:30
venkat102
c554e2cce7 fix(period closing voucher): closing account head debit and debit in account currency should be equal
(cherry picked from commit d6fd613272)
2025-07-16 14:46:26 +00:00
venkat102
98bd880c73 fix(period closing voucher): closing account head debit and debit in account currency should be equal
(cherry picked from commit d6fd613272)
2025-07-16 14:44:15 +00:00
rohitwaghchaure
7c2b32fb74 Merge pull request #48635 from frappe/mergify/bp/version-15-hotfix/pr-48633
fix: performance issue while submitting the purchase invoice (backport #48633)
2025-07-16 17:10:35 +05:30
Rohit Waghchaure
b9e6f524e5 fix: performance issue while submitting the purchase invoice
(cherry picked from commit 47979871de)
2025-07-16 11:16:36 +00:00
khushi8112
9fe1e6d0bd fix: test case 2025-07-16 16:03:07 +05:30
khushi8112
c7dcbed16f fix: test case 2025-07-16 15:41:42 +05:30
khushi8112
20bbfc504f fix: do not set value after depreciation as zero 2025-07-16 14:33:35 +05:30
khushi8112
3ab6a256e0 test: updated test case 2025-07-16 13:33:31 +05:30
khushi8112
d9b24a30eb fix: update asset value after revaluation cancellation 2025-07-16 13:12:51 +05:30
Khushi Rawat
7d686abe37 Merge pull request #48603 from frappe/mergify/bp/version-15-hotfix/pr-48360
fix: fetch sales invoice based on mode_of_payment in item-wise sales register (backport #48360)
2025-07-16 12:28:10 +05:30
mergify[bot]
2b1fdba7fd fix(transaction): recalculate tax and total when quantity changes (backport #48565) (#48625)
fix(transaction): recalculate tax and total when quantity changes

(cherry picked from commit ac7b6c6a3d)

Co-authored-by: Bhavan23 <bhavansathru.it@gmail.com>
2025-07-16 12:09:58 +05:30
Diptanil Saha
a46cafe652 fix: pos customer selection on new order (#48623) 2025-07-16 11:08:29 +05:30
Diptanil Saha
6b96a26462 Merge pull request #48614 from diptanilsaha/backport_48411
fix: employee search based on the fields mentioned in the employee doctype search fields (backport #48411)
2025-07-15 18:51:51 +05:30
rohitwaghchaure
01ed30ec01 Merge pull request #48599 from frappe/mergify/bp/version-15-hotfix/pr-48408
fix: resolve bundle item into line item if againt default supplier ch… (backport #48408)
2025-07-15 18:41:35 +05:30
rohitwaghchaure
34d4c32dfd Merge pull request #48613 from frappe/mergify/bp/version-15-hotfix/pr-48610
fix: added serial no condition (backport #48610)
2025-07-15 18:35:16 +05:30
Frappe PR Bot
b35f5aca91 chore(release): Bumped to Version 15.70.0
# [15.70.0](https://github.com/frappe/erpnext/compare/v15.69.2...v15.70.0) (2025-07-15)

### Bug Fixes

* employee_exit_translatability ([c894b18](c894b18165))
* **Employee:** add context to status in List View (backport [#48576](https://github.com/frappe/erpnext/issues/48576)) ([#48577](https://github.com/frappe/erpnext/issues/48577)) ([0e67487](0e67487508))
* error in available serial no report is no serial no present in company ([f1ff5a3](f1ff5a39ae))
* fetch item tax template after setting `base_net_rate` ([b5c4f61](b5c4f61fef))
* field name of price_list in material request ([ee6ef03](ee6ef03e24))
* fix party account field access ([54275db](54275dbe38))
* gross margin not set in project on submission of stock entry ([81e244b](81e244be55))
* handle cases where distributed discount amount is not set ([78df526](78df52606f))
* incorrect if condition ([a195152](a195152cc8))
* incorrect last sle for no batch wise valuation ([f2af2fe](f2af2fe63b))
* incorrect stock reco sle ([1322cc1](1322cc1378))
* incorrect test ([c57ca1a](c57ca1ae29))
* indicator in material_request_list.js ([4eb9f73](4eb9f73a52))
* invalid comparison error in sabb.py ([7ac5463](7ac546333a))
* make labels in serial_batch_prompt translatable ([c20a5b0](c20a5b01b4))
* missing parameter in precision function ([f80ad4e](f80ad4ee58))
* no attribute error in old subcontracting flow ([5fce819](5fce8191f9))
* pos adding item multiple times on item group filter ([3a70b5d](3a70b5d7fc))
* prevent creation of root accounts in account tree view ([817bcc7](817bcc78a0))
* prevent unnecessary db.commit ([00d39eb](00d39eb208))
* prevent unnecessary db.commit for contact insert [Linters] ([5cfeb29](5cfeb2978b))
* resolve sql error on dimension-wise accounts balance report (backport [#48477](https://github.com/frappe/erpnext/issues/48477)) ([#48478](https://github.com/frappe/erpnext/issues/48478)) ([243b533](243b533150))
* set value after depreciation when creating test asset ([4383d29](4383d29d7b))
* sort available batches based on expiry when merging SLEs with SABB and those without (backport [#48471](https://github.com/frappe/erpnext/issues/48471)) ([#48473](https://github.com/frappe/erpnext/issues/48473)) ([7a4c8d8](7a4c8d81e2))
* split and set value after depreciation ([3488ba0](3488ba05eb))
* stock settings save issue ([a5c49d1](a5c49d1e08))
* system was allowing credit notes with serial numbers for any customer ([4b6444e](4b6444e93b))
* updated test ([f35fd98](f35fd9842e))
* use `flt` value of bin qty ([fc8d451](fc8d451c55))
* use planned_qty instead of pending_qty to check if WO should be created against PP ([89660c9](89660c9070))

### Features

* add calculate_ageing_with option in summary reports ([72e154f](72e154fbb7))
* batch rate (valuation) in Batch-Wise Balance History report ([facd202](facd2027c3))
* **BOM:** improve tree display with item_name and qty (backport [#48176](https://github.com/frappe/erpnext/issues/48176)) ([#48494](https://github.com/frappe/erpnext/issues/48494)) ([fdd79c7](fdd79c7677))
* parent item group support in Stock Projected Qty report ([db525c2](db525c2538))
* update the modified date of the SLE after reposting ([8c77ea1](8c77ea16cf))

### Performance Improvements

* optimize code for subcontracting ([9aef305](9aef3058a6))
* use `cached_doc` for Account Settings ([f1cdd76](f1cdd76fc1))
2025-07-15 12:52:04 +00:00
ruthra kumar
e101849fb2 Merge pull request #48602 from frappe/version-15-hotfix
chore: release v15
2025-07-15 18:20:37 +05:30
rohitwaghchaure
873e0a4219 chore: fix conflicts 2025-07-15 18:05:31 +05:30
Rohit Waghchaure
467fe1d72f fix: added serial no condition
(cherry picked from commit bb7ddd11f1)
2025-07-15 12:35:27 +00:00
Sagar Vora
6b68a50cc4 Merge pull request #48608 from frappe/mergify/bp/version-15-hotfix/pr-48607
fix: fix party account field access (backport #48607)
2025-07-15 11:28:24 +00:00
ljain112
54275dbe38 fix: fix party account field access
(cherry picked from commit 0da8ed2daa)
2025-07-15 11:25:17 +00:00
rohitwaghchaure
9070c4302d Merge pull request #48598 from frappe/mergify/bp/version-15-hotfix/pr-48595
fix: system was allowing credit notes with serial numbers for any customer (backport #48595)
2025-07-15 16:35:29 +05:30
Khushi Rawat
47878758fc Merge pull request #48589 from khushi8112/update-value-after-depreciation-when-asset-splitting
fix: update value after depreciation when asset splitting
2025-07-15 15:54:08 +05:30
ravibharathi656
d04c256b73 fix: fetch sales invoice based on mode_of_payment in item-wise sales register
(cherry picked from commit 39cd7a29df)
2025-07-15 10:21:05 +00:00
khushi8112
c57ca1ae29 fix: incorrect test 2025-07-15 15:36:13 +05:30
ruthra kumar
0a9f45aacf Merge pull request #48600 from frappe/mergify/bp/version-15-hotfix/pr-48535
fix: handle cases where distributed discount amount is not set (backport #48535)
2025-07-15 15:34:45 +05:30
ljain112
78df52606f fix: handle cases where distributed discount amount is not set
(cherry picked from commit 816b84be02)
2025-07-15 09:38:01 +00:00
l0gesh29
6fddf4c5aa fix: carry forward the delivered_by_supplier check to PO
(cherry picked from commit f3460ec840)

# Conflicts:
#	erpnext/stock/doctype/packed_item/packed_item.json
2025-07-15 09:37:02 +00:00
l0gesh29
725f9ea012 fix: resolve bundle item into line item if againt default supplier checked
(cherry picked from commit ec07549d5e)
2025-07-15 09:37:02 +00:00
rohitwaghchaure
3100099cfa chore: fix conflicts 2025-07-15 15:04:18 +05:30
rohitwaghchaure
9aeb08f968 chore: fix conflicts 2025-07-15 15:03:41 +05:30
Rohit Waghchaure
4b6444e93b fix: system was allowing credit notes with serial numbers for any customer
(cherry picked from commit e073075834)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/delivery_note.py
#	erpnext/stock/doctype/serial_no/serial_no.json
2025-07-15 09:28:35 +00:00
rohitwaghchaure
0151733a25 Merge pull request #48596 from frappe/mergify/bp/version-15-hotfix/pr-48588
perf: optimize code for subcontracting (backport #48588)
2025-07-15 14:55:57 +05:30
khushi8112
4383d29d7b fix: set value after depreciation when creating test asset 2025-07-15 14:34:00 +05:30
Mihir Kandoi
9aef3058a6 perf: optimize code for subcontracting
(cherry picked from commit bc6f69ad54)
2025-07-15 08:45:36 +00:00
khushi8112
68162f79a1 chore: run pre-commit 2025-07-15 13:40:00 +05:30
khushi8112
f35fd9842e fix: updated test 2025-07-15 13:30:35 +05:30
ruthra kumar
ba63f27e3c Merge pull request #48587 from frappe/mergify/bp/version-15-hotfix/pr-48582
fix: make labels in serial_batch_prompt translatable (backport #48582)
2025-07-15 12:23:51 +05:30
khushi8112
3488ba05eb fix: split and set value after depreciation 2025-07-15 12:22:50 +05:30
barredterra
c20a5b01b4 fix: make labels in serial_batch_prompt translatable
(cherry picked from commit 8757800888)
2025-07-15 06:25:37 +00:00
ruthra kumar
c20c9031f4 Merge pull request #48574 from frappe/mergify/bp/version-15-hotfix/pr-47892
refactor: use sql for building voucher balance in Receivable report (backport #47892)
2025-07-15 07:20:37 +05:30
Mihir Kandoi
16cb147d86 Merge pull request #48569 from frappe/mergify/bp/version-15-hotfix/pr-48542
fix: field name of price_list in material request (backport #48542)
2025-07-14 21:31:54 +05:30
mergify[bot]
0e67487508 fix(Employee): add context to status in List View (backport #48576) (#48577)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Employee): add context to status in List View (#48576)
2025-07-14 15:42:30 +02:00
rohitwaghchaure
2a8b8aa71d Merge pull request #48570 from frappe/mergify/bp/version-15-hotfix/pr-48568
fix: recalculate qty issue for stock reco (backport #48568)
2025-07-14 18:16:48 +05:30
ruthra kumar
3b38c6708c chore: rename method
(cherry picked from commit fc8ca7d82c)
2025-07-14 12:33:08 +00:00
ruthra kumar
72b9684742 refactor: build and pass match conditions as qb criterion
(cherry picked from commit 7efeed54de)
2025-07-14 12:33:08 +00:00
ruthra kumar
1d18a3b1a3 chore: drop unused utility method
(cherry picked from commit 52c0df24e3)
2025-07-14 12:33:08 +00:00
ruthra kumar
0bf5d3dae3 refactor: dynamic DB field types
(cherry picked from commit 9d0ebe3427)
2025-07-14 12:33:07 +00:00
ruthra kumar
d9b36ea37c refactor: better variable name
(cherry picked from commit 1a90c0d031)
2025-07-14 12:33:07 +00:00
ruthra kumar
2d2ca049fa refactor: prefix-ed names for easy distinction
(cherry picked from commit c5e35cc330)
2025-07-14 12:33:07 +00:00
ruthra kumar
1afb27231c refactor: utility to drop existing procedures and include cost center
(cherry picked from commit da32bb5f51)
2025-07-14 12:33:06 +00:00
ruthra kumar
a173c77859 refactor: order by posting date
(cherry picked from commit 7b7440d44a)
2025-07-14 12:33:06 +00:00
ruthra kumar
5d0d0c3102 refactor: call procedures based on config
(cherry picked from commit e90c6a33bd)
2025-07-14 12:33:06 +00:00
ruthra kumar
92d58a4e4c refactor: introduce sql option for data fetch
(cherry picked from commit 8cf8f6abad)
2025-07-14 12:33:05 +00:00
ruthra kumar
fee646fbe2 refactor: better readability
(cherry picked from commit 097e74979f)
2025-07-14 12:33:05 +00:00
ruthra kumar
c6d82b241e refactor: using sql procedures for AR report
- dynamic filters are passed

(cherry picked from commit e5920c57aa)
2025-07-14 12:33:04 +00:00
Mihir Kandoi
e3f7915c38 Merge pull request #48571 from frappe/mergify/bp/version-15-hotfix/pr-48526
fix: gross margin not set in project on submission of stock entry (backport #48526)
2025-07-14 17:31:46 +05:30
Mihir Kandoi
c39993a3ba chore: resolve conflicts 2025-07-14 17:22:00 +05:30
Mihir Kandoi
d8212d98ca chore: resolve conflicts 2025-07-14 17:20:28 +05:30
Mihir Kandoi
e3ba4320d6 chore: resolve conflicts 2025-07-14 17:19:40 +05:30
Mihir Kandoi
81e244be55 fix: gross margin not set in project on submission of stock entry
(cherry picked from commit ec578ba231)
2025-07-14 11:43:16 +00:00
Mihir Kandoi
a195152cc8 fix: incorrect if condition
(cherry picked from commit 668574e4f0)
2025-07-14 11:38:10 +00:00
Mihir Kandoi
1322cc1378 fix: incorrect stock reco sle
(cherry picked from commit 597d5aff02)
2025-07-14 11:38:09 +00:00
Mihir Kandoi
ee6ef03e24 fix: field name of price_list in material request
(cherry picked from commit adb9a6bc15)

# Conflicts:
#	erpnext/patches.txt
#	erpnext/stock/doctype/material_request/material_request.py
#	erpnext/stock/doctype/packed_item/packed_item.py
2025-07-14 11:14:53 +00:00
ruthra kumar
6e10b53e24 Merge pull request #48567 from frappe/mergify/bp/version-15-hotfix/pr-48321
fix: fetch item tax template after setting `base_net_rate` (backport #48321)
2025-07-14 15:06:32 +05:30
ljain112
b5c4f61fef fix: fetch item tax template after setting base_net_rate
(cherry picked from commit db654d5e59)
2025-07-14 09:32:56 +00:00
ruthra kumar
9750eba9db Merge pull request #48566 from frappe/mergify/bp/version-15-hotfix/pr-48435
fix: prevent creation of root accounts in account tree view (backport #48435)
2025-07-14 14:51:17 +05:30
ljain112
817bcc78a0 fix: prevent creation of root accounts in account tree view
(cherry picked from commit 3600f2f91b)
2025-07-14 09:15:45 +00:00
ruthra kumar
69f2d751a2 Merge pull request #48562 from frappe/mergify/bp/version-15-hotfix/pr-48540
feat: add calculate_ageing_with option in summary reports (backport #48540)
2025-07-14 11:42:13 +05:30
ruthra kumar
9287e9a2ca Merge pull request #48556 from frappe/mergify/bp/version-15-hotfix/pr-48550
chore: fix flacky test and remove redundant code (backport #48550)
2025-07-14 11:41:36 +05:30
l0gesh29
72e154fbb7 feat: add calculate_ageing_with option in summary reports
(cherry picked from commit a3834eef46)
2025-07-14 06:08:52 +00:00
Sagar Vora
d5faae3e7c Merge pull request #48081 from frappe/mergify/bp/version-15-hotfix/pr-48048
fix: use `flt` value of bin qty (backport #48048)
2025-07-14 05:17:07 +00:00
Sagar Vora
f4a79bb760 Merge pull request #48537 from frappe/mergify/bp/version-15-hotfix/pr-48495 2025-07-14 05:15:01 +00:00
ljain112
153df4eca5 chore: resolve conflicts 2025-07-13 18:16:49 +05:30
ljain112
8f6cd40c7b chore: return doc if item already exists for test
(cherry picked from commit e6b9e82b2f)
2025-07-13 11:51:32 +00:00
ljain112
567f7b4d71 chore: fix flacky test and remove redundant code
(cherry picked from commit de8c3ba968)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
2025-07-13 11:51:32 +00:00
Frappe PR Bot
cad5f39b7f chore(release): Bumped to Version 15.69.2
## [15.69.2](https://github.com/frappe/erpnext/compare/v15.69.1...v15.69.2) (2025-07-11)

### Bug Fixes

* incorrect last sle for no batch wise valuation ([35451fd](35451fd298))
2025-07-11 09:55:30 +00:00
rohitwaghchaure
1e2845c277 Merge pull request #48527 from frappe/mergify/bp/version-15/pr-48525
fix: incorrect last sle for no batch wise valuation (backport #48512) (backport #48525)
2025-07-11 15:23:58 +05:30
ljain112
f1cdd76fc1 perf: use cached_doc for Account Settings
(cherry picked from commit 751f3abd95)
2025-07-11 09:15:52 +00:00
Asmita Hase
715f4025cc Merge pull request #48531 from frappe/mergify/bp/version-15-hotfix/pr-48395
fix: employee_exit_translatability (backport #48395)
2025-07-11 12:37:17 +05:30
mahsem
c894b18165 fix: employee_exit_translatability
(cherry picked from commit 80d6779210)
2025-07-11 06:33:22 +00:00
Rohit Waghchaure
35451fd298 fix: incorrect last sle for no batch wise valuation
(cherry picked from commit 93d3eb662f)
(cherry picked from commit f2af2fe63b)
2025-07-11 05:49:11 +00:00
rohitwaghchaure
43e4c00e1c Merge pull request #48525 from frappe/mergify/bp/version-15-hotfix/pr-48512
fix: incorrect last sle for no batch wise valuation (backport #48512)
2025-07-11 11:18:09 +05:30
Rohit Waghchaure
f2af2fe63b fix: incorrect last sle for no batch wise valuation
(cherry picked from commit 93d3eb662f)
2025-07-11 05:33:01 +00:00
Mihir Kandoi
76fe861281 Merge pull request #48516 from frappe/mergify/bp/version-15-hotfix/pr-48514
fix: no attribute error in old subcontracting flow (backport #48514)
2025-07-11 10:23:39 +05:30
Mihir Kandoi
39e2c87955 Merge pull request #48515 from frappe/mergify/bp/version-15-hotfix/pr-48513
fix: error in available serial no report if no serial no present in company (backport #48513)
2025-07-11 10:23:10 +05:30
Mihir Kandoi
5fce8191f9 fix: no attribute error in old subcontracting flow
(cherry picked from commit 51751a7a05)
2025-07-10 17:33:10 +00:00
Mihir Kandoi
f1ff5a39ae fix: error in available serial no report is no serial no present in company
(cherry picked from commit 0ae60b8b61)
2025-07-10 17:21:58 +00:00
Mihir Kandoi
f70ce62f2a Merge pull request #48504 from frappe/mergify/bp/version-15-hotfix/pr-48503
fix: invalid comparison error in sabb.py (backport #48503)
2025-07-10 21:36:56 +05:30
Mihir Kandoi
36af50b2ce Merge pull request #48511 from frappe/mergify/bp/version-15-hotfix/pr-48510
fix: missing parameter in precision function (backport #48510)
2025-07-10 21:36:08 +05:30
Mihir Kandoi
f80ad4ee58 fix: missing parameter in precision function
(cherry picked from commit 3886641887)
2025-07-10 13:39:28 +00:00
Diptanil Saha
5f41abbdf7 Merge pull request #48507 from frappe/mergify/bp/version-15-hotfix/pr-48506
fix: pos adding item multiple times on applying item group filter (backport #48506)
2025-07-10 12:42:00 +05:30
diptanilsaha
3a70b5d7fc fix: pos adding item multiple times on item group filter
(cherry picked from commit e9f99e5a3f)
2025-07-10 07:09:28 +00:00
ruthra kumar
0a447caa8e Merge pull request #48505 from frappe/mergify/bp/version-15-hotfix/pr-45300
fix: prevent unnecessary db.commit for contact insert (backport #45300)
2025-07-10 11:34:12 +05:30
HarryPaulo
5cfeb2978b fix: prevent unnecessary db.commit for contact insert [Linters]
(cherry picked from commit 87de5c7450)
2025-07-10 05:43:14 +00:00
HarryPaulo
00d39eb208 fix: prevent unnecessary db.commit
(cherry picked from commit 5f15b0b65b)
2025-07-10 05:43:14 +00:00
Mihir Kandoi
7ac546333a fix: invalid comparison error in sabb.py
(cherry picked from commit ec1faf02ed)
2025-07-10 05:35:48 +00:00
Mihir Kandoi
c1334ea2cb Merge pull request #48501 from frappe/mergify/bp/version-15-hotfix/pr-48499 2025-07-09 21:44:35 +05:30
Mihir Kandoi
89660c9070 fix: use planned_qty instead of pending_qty to check if WO should be created against PP
(cherry picked from commit b11bf8eb79)
2025-07-09 15:56:34 +00:00
Frappe PR Bot
76f7eb0f9f chore(release): Bumped to Version 15.69.1
## [15.69.1](https://github.com/frappe/erpnext/compare/v15.69.0...v15.69.1) (2025-07-09)

### Bug Fixes

* use planned_qty instead of pending_qty to check if WO should be created against PP ([9967b1c](9967b1ce4a))
2025-07-09 15:40:37 +00:00
Mihir Kandoi
9c34b90328 Merge pull request #48500 from frappe/mergify/bp/version-15/pr-48499
fix: use planned_qty instead of pending_qty to check if WO should be created against PP (backport #48499)
2025-07-09 21:09:06 +05:30
Mihir Kandoi
9967b1ce4a fix: use planned_qty instead of pending_qty to check if WO should be created against PP
(cherry picked from commit b11bf8eb79)
2025-07-09 15:35:22 +00:00
rohitwaghchaure
4d10cb2727 Merge pull request #48497 from frappe/mergify/bp/version-15-hotfix/pr-48490
feat: update the modified date of the SLE after reposting (backport #48490)
2025-07-09 19:45:12 +05:30
Rohit Waghchaure
8c77ea16cf feat: update the modified date of the SLE after reposting
(cherry picked from commit c2cd4934e7)
2025-07-09 13:52:15 +00:00
rohitwaghchaure
2c59874fd8 Merge pull request #48492 from frappe/mergify/bp/version-15-hotfix/pr-48488
feat: parent item group support in Stock Projected Qty report (backport #48488)
2025-07-09 18:59:38 +05:30
mergify[bot]
fdd79c7677 feat(BOM): improve tree display with item_name and qty (backport #48176) (#48494)
Co-authored-by: Patrick Eißler <77415730+PatrickDEissler@users.noreply.github.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-07-09 14:13:24 +02:00
Rohit Waghchaure
db525c2538 feat: parent item group support in Stock Projected Qty report
(cherry picked from commit 6e80d89d13)
2025-07-09 11:20:04 +00:00
rohitwaghchaure
142de2e2a7 Merge pull request #48489 from frappe/mergify/bp/version-15-hotfix/pr-48487
feat: batch rate (valuation) in Batch-Wise Balance History report (backport #48487)
2025-07-09 16:48:05 +05:30
Rohit Waghchaure
facd2027c3 feat: batch rate (valuation) in Batch-Wise Balance History report
(cherry picked from commit 8a2a845a16)
2025-07-09 10:59:11 +00:00
rohitwaghchaure
90979860cb Merge pull request #48482 from frappe/mergify/bp/version-15-hotfix/pr-48481
fix: stock settings save issue (backport #48481)
2025-07-09 14:16:18 +05:30
Rohit Waghchaure
a5c49d1e08 fix: stock settings save issue
(cherry picked from commit 64ae1ec367)
2025-07-09 06:34:10 +00:00
mergify[bot]
243b533150 fix: resolve sql error on dimension-wise accounts balance report (backport #48477) (#48478)
fix: resolve sql error on dimension-wise accounts balance report (#48477)

(cherry picked from commit c714b724da)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-07-08 23:50:20 +05:30
Mihir Kandoi
85cb234376 Merge pull request #48476 from frappe/mergify/bp/version-15-hotfix/pr-48475
fix: indicator in material_request_list.js (backport #48475)
2025-07-08 23:07:32 +05:30
Mihir Kandoi
4eb9f73a52 fix: indicator in material_request_list.js
(cherry picked from commit d10647a592)
2025-07-08 16:46:16 +00:00
mergify[bot]
7a4c8d81e2 fix: sort available batches based on expiry when merging SLEs with SABB and those without (backport #48471) (#48473)
* fix: sort available batches based on expiry

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-07-08 21:23:30 +05:30
Abdeali Chharchhoda
fc8d451c55 fix: use flt value of bin qty
(cherry picked from commit 0a8e42a358)
2025-06-16 12:54:44 +00:00
246 changed files with 5715 additions and 1355 deletions

25
.github/workflows/patch_faux.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
# Tests are skipped for these files but github doesn't allow "passing" hence this is required.
name: Skipped Patch Test
on:
pull_request:
paths:
- "**.js"
- "**.css"
- "**.md"
- "**.html"
- "**.csv"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
name: Patch Test
steps:
- name: Pass skipped tests unconditionally
run: "echo Skipped"

View File

@@ -0,0 +1,27 @@
# Tests are skipped for these files but github doesn't allow "passing" hence this is required.
name: Skipped Tests
on:
pull_request:
paths:
- "**.js"
- "**.css"
- "**.md"
- "**.html"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
container: [1, 2, 3, 4]
name: Python Unit Tests
steps:
- name: Pass skipped tests unconditionally
run: "echo Skipped"

View File

@@ -3,22 +3,21 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @khushi8112 @deepeshgarg007
erpnext/regional @deepeshgarg007 @ruthra-kumar
erpnext/selling @deepeshgarg007 @ruthra-kumar
erpnext/support/ @deepeshgarg007
pos*
erpnext/accounts/ @ruthra-kumar
erpnext/assets/ @khushi8112
erpnext/regional @ruthra-kumar
erpnext/selling @ruthra-kumar
erpnext/support/ @ruthra-kumar
erpnext/buying/ @rohitwaghchaure
erpnext/buying/ @rohitwaghchaure @mihir-kandoi
erpnext/maintenance/ @rohitwaghchaure
erpnext/manufacturing/ @rohitwaghchaure
erpnext/manufacturing/ @rohitwaghchaure @mihir-kandoi
erpnext/quality_management/ @rohitwaghchaure
erpnext/stock/ @rohitwaghchaure
erpnext/subcontracting @rohitwaghchaure
erpnext/stock/ @rohitwaghchaure @mihir-kandoi
erpnext/subcontracting @mihir-kandoi
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure
erpnext/patches/ @deepeshgarg007
erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
erpnext/patches/ @ruthra-kumar
.github/ @deepeshgarg007
.github/ @ruthra-kumar
pyproject.toml @akhilnarang

View File

@@ -4,7 +4,7 @@ import inspect
import frappe
from frappe.utils.user import is_website_user
__version__ = "15.69.0"
__version__ = "15.76.0"
def get_default_company(user=None):

View File

@@ -169,7 +169,7 @@ class Account(NestedSet):
if par.root_type:
self.root_type = par.root_type
if self.is_group:
if cint(self.is_group):
db_value = self.get_doc_before_save()
if db_value:
if self.report_type != db_value.report_type:
@@ -212,7 +212,7 @@ class Account(NestedSet):
if doc_before_save and not doc_before_save.parent_account:
throw(_("Root cannot be edited."), RootNotEditable)
if not self.parent_account and not self.is_group:
if not self.parent_account and not cint(self.is_group):
throw(_("The root account {0} must be a group").format(frappe.bold(self.name)))
def validate_root_company_and_sync_account_to_children(self):
@@ -261,7 +261,7 @@ class Account(NestedSet):
if self.check_gle_exists():
throw(_("Account with existing transaction cannot be converted to ledger"))
elif self.is_group:
elif cint(self.is_group):
if self.account_type and not self.flags.exclude_account_type_check:
throw(_("Cannot covert to Group because Account Type is selected."))
elif self.check_if_child_exists():
@@ -304,7 +304,9 @@ class Account(NestedSet):
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
self.currency_explicitly_specified = False
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency")
gl_currency = frappe.db.get_value(
"GL Entry", {"account": self.name, "is_cancelled": 0}, "account_currency"
)
if gl_currency and self.account_currency != gl_currency:
if frappe.db.get_value("GL Entry", {"account": self.name}):

View File

@@ -286,12 +286,14 @@ frappe.treeview_settings["Account"] = {
label: __("View Ledger"),
click: function (node, btn) {
frappe.route_options = {
account: node.label,
from_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
to_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
company:
frappe.treeview_settings["Account"].treeview.page.fields_dict.company.get_value(),
};
if (node.parent_label) {
frappe.route_options["account"] = node.label;
}
frappe.set_route("query-report", "General Ledger");
},
btnClass: "hidden-xs",

View File

@@ -0,0 +1,817 @@
{
"country_code": "au",
"name": "Australia - Chart of Accounts with Account Numbers",
"tree": {
"Assets": {
"Current Assets": {
"Cash On Hand": {
"Cash On Hand": {
"account_number": "11010",
"account_type": "Cash"
},
"account_number": "110",
"is_group": 1
},
"Cash at Bank": {
"Every Day Bank Account": {
"account_number": "11510",
"account_type": "Bank"
},
"Business Savings Account": {
"account_number": "11520"
},
"Business Term Deposit": {
"account_number": "11530"
},
"account_number": "115",
"is_group": 1
},
"Trade Receivables": {
"Trade Debtors": {
"account_number": "12010",
"account_type": "Receivable"
},
"Provision for Doubtful Debts": {
"account_number": "12020"
},
"Sundry Debtors": {
"account_number": "12030"
},
"Debtor Refund": {
"account_number": "12040"
},
"account_number": "120",
"is_group": 1
},
"Inventory": {
"Stock On Hand": {
"account_number": "13010",
"account_type": "Stock"
},
"WIP - Work In Progress - Manufacturing": {
"account_number": "13020"
},
"account_number": "130",
"is_group": 1
},
"Prepayments": {
"Prepayments": {
"account_number": "14010"
},
"Provisional Tax Paid": {
"account_number": "14020"
},
"account_number": "140",
"is_group": 1
},
"account_number": "11",
"is_group": 1
},
"Non Current Assets": {
"Plant & Equipment": {
"Plant & Equipment": {
"account_number": "16010",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation Plant & Equipment": {
"account_number": "16020",
"account_type": "Accumulated Depreciation"
},
"account_number": "160",
"is_group": 1
},
"Motor Vehicle": {
"Motor Vehicle": {
"account_number": "16110",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation Motor Vehicle": {
"account_number": "16120",
"account_type": "Accumulated Depreciation"
},
"account_number": "161",
"is_group": 1
},
"Office Equipment": {
"Office Furniture & Equipment": {
"account_number": "16210",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation Office Furniture & Equipment": {
"account_number": "16220",
"account_type": "Accumulated Depreciation"
},
"account_number": "162",
"is_group": 1
},
"Computer Equipment": {
"Computer Equipment": {
"account_number": "16310",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation Computer Equipment": {
"account_number": "16320",
"account_type": "Accumulated Depreciation"
},
"account_number": "163",
"is_group": 1
},
"Building": {
"Buildings": {
"account_number": "16410",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation Buildings": {
"account_number": "16420",
"account_type": "Accumulated Depreciation"
},
"CWIP - Construction Work In Progress": {
"account_number": "16430",
"account_type": "Capital Work in Progress"
},
"Accumulated Depreciation - Others": {
"account_number": "16440",
"account_type": "Accumulated Depreciation"
},
"account_number": "164",
"is_group": 1
},
"Related Party": {
"Loan to Party 1": {
"account_number": "17010"
},
"account_number": "170",
"is_group": 1
},
"Investments & Unlisted Entities": {
"Investment - Entity 1": {
"account_number": "17510"
},
"account_number": "175",
"is_group": 1
},
"Intagible Assets": {
"Goodwill": {
"account_number": "18010"
},
"Opening Balance Temporary ": {
"account_number": "18090",
"account_type": "Temporary"
},
"account_number": "180",
"is_group": 1
},
"account_number": "16",
"is_group": 1
},
"account_number": "1",
"root_type": "Asset"
},
"Liabilities": {
"Current Liabilities": {
"Trade Payables - Current": {
"Trade Creditors": {
"account_number": "21010",
"account_type": "Payable"
},
"Goods Received Not Invoiced": {
"account_number": "21050",
"account_type": "Stock Received But Not Billed"
},
"Service Received Not Invoiced": {
"account_number": "21060"
},
"Asset Received Not Invoiced": {
"account_number": "21070",
"account_type": "Asset Received But Not Billed"
},
"account_number": "210",
"is_group": 1
},
"Other Payables - Current": {
"Accrued Expenses": {
"account_number": "21510"
},
"Payroll - Wages Clearing": {
"account_number": "21550"
},
"Payroll - Superannuation Deductions": {
"account_number": "21555"
},
"Payroll - Misc Deductions": {
"account_number": "21560"
},
"Payroll - Withholding Tax Payable": {
"account_number": "21565"
},
"account_number": "215",
"is_group": 1
},
"GST": {
"GST Payments to ATO": {
"account_number": "22030"
},
"Provision for PAYG Tax": {
"account_number": "22040"
},
"account_number": "220",
"account_type": "Tax",
"is_group": 1
},
"Interest & Non Bearing Liabilities - Current": {
"Credit Card - VISA": {
"account_number": "22510"
},
"account_number": "225",
"is_group": 1
},
"Bank Overdraft": {
"Bank Overdraft Cash at Bank": {
"account_number": "23010"
},
"account_number": "230",
"is_group": 1
},
"Trade Finance": {
"Trade Finance": {
"account_number": "23510"
},
"account_number": "235",
"is_group": 1
},
"Lease Liabilities": {
"Finance Lease - Current": {
"account_number": "24010"
},
"account_number": "240",
"is_group": 1
},
"Provisions": {
"Provision for Long Service Leave": {
"account_number": "24510"
},
"Provision for Holiday Pay": {
"account_number": "24520"
},
"account_number": "245",
"is_group": 1
},
"account_number": "21",
"is_group": 1
},
"Non Current Liabilities": {
"Trade & Other Payables - Non Current": {
"Loan Account - Party 1": {
"account_number": "25010"
},
"account_number": "250",
"is_group": 1
},
"Interest & Non Bearing Liabilities - Non Current": {
"Non Current Liability - Director Loan": {
"account_number": "25510"
},
"account_number": "255",
"is_group": 1
},
"Bank Loans - Non Current": {
"Bank Loan 1 - Non Current": {
"account_number": "26010"
},
"account_number": "260",
"is_group": 1
},
"Lease Liabilities - Non Current": {
"Finance Lease - Non Current": {
"account_number": "27010"
},
"account_number": "270",
"is_group": 1
},
"Provisions - Non Current": {
"Provision for Long Service Leave": {
"account_number": "27510"
},
"Provision for Holiday Pay": {
"account_number": "27520"
},
"account_number": "275",
"is_group": 1
},
"account_number": "25",
"is_group": 1
},
"account_number": "2",
"root_type": "Liability"
},
"Equity": {
"Equity": {
"Owner's/Shareholder's Equity": {
"Owner's/Shareholders Capital": {
"account_number": "31010",
"account_type": "Equity"
},
"Owner's/Shareholders Drawings": {
"account_number": "31020",
"account_type": "Equity"
},
"account_number": "310",
"is_group": 1
},
"Earnings": {
"Current Year Earnings": {
"account_number": "35010",
"account_type": "Equity"
},
"Retained Earnings": {
"account_number": "35020",
"account_type": "Equity"
},
"account_number": "350",
"is_group": 1
},
"account_number": "31",
"is_group": 1
},
"account_number": "3",
"root_type": "Equity"
},
"Revenue": {
"Revenue": {
"Sales Revenue": {
"Sales Income": {
"account_number": "41010",
"account_type": "Income Account"
},
"Freight Income": {
"account_number": "41020",
"account_type": "Income Account"
},
"Other Income": {
"account_number": "41030",
"account_type": "Income Account"
},
"Service Income": {
"account_number": "41040",
"account_type": "Income Account"
},
"account_number": "410",
"is_group": 1
},
"Other Revenue": {
"Commission Received": {
"account_number": "42010"
},
"Discounts Received": {
"account_number": "42020"
},
"Interest received": {
"account_number": "42030"
},
"Profit/Loss on Sales of Assets": {
"account_number": "42040"
},
"Rent Received": {
"account_number": "42050"
},
"Sundry Income": {
"account_number": "42060"
},
"account_number": "420",
"is_group": 1
},
"account_number": "41",
"is_group": 1
},
"account_number": "4",
"root_type": "Income"
},
"Cost of Goods": {
"Cost of Goods": {
"Cost of Goods Sold": {
"Cost of Goods Sold": {
"account_number": "51010",
"account_type": "Cost of Goods Sold"
},
"Freight Expenses (sales related)": {
"account_number": "51020"
},
"Discounts Given": {
"account_number": "51030"
},
"Subcontracting Charges": {
"account_number": "51040"
},
"account_number": "510",
"is_group": 1
},
"Other COGS": {
"Purchases - Miscellaneous": {
"account_number": "52010"
},
"Duty & Customs Fees": {
"account_number": "52020",
"account_type": "Tax"
},
"Freight Inwards": {
"account_number": "52030",
"account_type": "Chargeable"
},
"Stock Adjustment": {
"account_number": "52040",
"account_type": "Stock Adjustment"
},
"Stock Wirte Off": {
"account_number": "52050",
"account_type": "Stock Adjustment"
},
"Stock Valuation Expenses": {
"account_number": "52060",
"account_type": "Expenses Included In Valuation"
},
"Asset Valuation Expenses": {
"account_number": "52070",
"account_type": "Expenses Included In Asset Valuation"
},
"account_number": "520",
"is_group": 1
},
"account_number": "51",
"is_group": 1
},
"account_number": "5",
"root_type": "Expense"
},
"Expenses": {
"Fixed Expenses": {
"Payroll & Related Expenses": {
"Salaries & Wages": {
"account_number": "61010"
},
"Superannuation": {
"account_number": "61015"
},
"Staff Amenities - GST Paid": {
"account_number": "61020"
},
"Staff Amenities - GST Free": {
"account_number": "61025"
},
"Staff Recruitment": {
"account_number": "61030"
},
"Staff Training": {
"account_number": "61035"
},
"Fringe Benefits Tax": {
"account_number": "61040"
},
"Payroll Tax": {
"account_number": "61045"
},
"Workers Compensation": {
"account_number": "61050"
},
"Long Service Leave": {
"account_number": "61060"
},
"Mileage Reimbursement": {
"account_number": "61070"
},
"Overtime": {
"account_number": "61080"
},
"Worksafe Insurance": {
"account_number": "61090"
},
"account_number": "610",
"is_group": 1
},
"Depreciation Expenses": {
"Depreciation - Plant & Equipment": {
"account_number": "62010",
"account_type": "Depreciation"
},
"Depreciation - Motor Vehicle": {
"account_number": "62020",
"account_type": "Depreciation"
},
"Depreciation - Office Equipment": {
"account_number": "62030",
"account_type": "Depreciation"
},
"Depreciation - Computer Equipment": {
"account_number": "62040",
"account_type": "Depreciation"
},
"Depreciation - Building": {
"account_number": "62050",
"account_type": "Depreciation"
},
"Depreciation - Others": {
"account_number": "62510",
"account_type": "Depreciation"
},
"account_number": "620",
"is_group": 1
},
"account_number": "61",
"is_group": 1
},
"Accrued Expenses": {
"Accrued Expenses": {
"Accrued Expenses - Salaries & Wages": {
"account_number": "63010"
},
"Accrued Expenses - Interest": {
"account_number": "63020"
},
"account_number": "630",
"is_group": 1
},
"account_number": "63",
"is_group": 1
},
"Operating Expenses": {
"General and Administrative Expenses": {
"Low Value Assets less than $300": {
"account_number": "64010"
},
"Office Supplies": {
"account_number": "64020"
},
"Postage & Courier": {
"account_number": "64025"
},
"Printing & Stationery": {
"account_number": "64030"
},
"Registration Fees / Filing Fees": {
"account_number": "64040"
},
"Travel & Accommodation - Local": {
"account_number": "64050"
},
"Travel & Accommodation - Overseas": {
"account_number": "64060"
},
"Relocation Costs": {
"account_number": "64070"
},
"Hire Charges": {
"account_number": "64080"
},
"Repairs & Maintenance": {
"account_number": "64210"
},
"Cleaning Expenses": {
"account_number": "64215"
},
"Uniforms": {
"account_number": "64220"
},
"Security": {
"account_number": "64225"
},
"Subscriptions & Licences": {
"account_number": "64510"
},
"Software Expenses": {
"account_number": "64515"
},
"Marketing Expenses": {
"account_number": "64520"
},
"Advertising Expenses": {
"account_number": "64525"
},
"Website Hosting & Domain Expenses": {
"account_number": "64530"
},
"Computer Repairs / Supplies": {
"account_number": "64540"
},
"Conferences": {
"account_number": "64550"
},
"Consultancy /Contract Services": {
"account_number": "64560"
},
"Training Services": {
"account_number": "64570"
},
"Workshop Supplies": {
"account_number": "64580"
},
"Consumables": {
"account_number": "64585"
},
"Entertainment Expenses - Deductible": {
"account_number": "64810"
},
"Entertainment Expenses - Non Deductible": {
"account_number": "64820"
},
"Amortisation Of Goodwill": {
"account_number": "64910"
},
"General / Miscellaneous Expenses": {
"account_number": "64915",
"account_type": "Chargeable"
},
"Donations": {
"account_number": "64920"
},
"Client Gifts": {
"account_number": "64930"
},
"Employee Gifts": {
"account_number": "64935"
},
"account_number": "640",
"is_group": 1
},
"Occupancy Expenses": {
"Rental Expenses": {
"account_number": "65010"
},
"Property Insurance": {
"account_number": "65020"
},
"Electricity Expenses": {
"account_number": "65030"
},
"Water Rates": {
"account_number": "65040"
},
"Gas Expenses": {
"account_number": "65050"
},
"Property Taxes": {
"account_number": "65060"
},
"Rates": {
"account_number": "65070"
},
"account_number": "650",
"is_group": 1
},
"Communication & Vehicle Expenses": {
"Internet Expenses": {
"account_number": "66010"
},
"Mobile Telephone": {
"account_number": "66020"
},
"Telephone Expenses": {
"account_number": "66030"
},
"Motor Vehicle - Fuel Expenses": {
"account_number": "66040"
},
"Motor Vehicle - Parking & Tolls": {
"account_number": "66050"
},
"Motor Vehicle - Registration & Insurance": {
"account_number": "66060"
},
"Motor Vehicle - Service & Repairs": {
"account_number": "66070"
},
"Taxi": {
"account_number": "66080"
},
"account_number": "660",
"is_group": 1
},
"account_number": "64",
"is_group": 1
},
"Non-Operating Expenses": {
"Finance Costs": {
"Interest - Bank Loans": {
"account_number": "67010"
},
"Interest - Finance Leases": {
"account_number": "67020"
},
"Interest - Other Loans": {
"account_number": "67025"
},
"Insurance": {
"account_number": "67030"
},
"Bank Charges": {
"account_number": "67050"
},
"Rounding off": {
"account_number": "67055",
"account_type": "Round Off"
},
"Audit Fees": {
"account_number": "67060"
},
"Accounting Fees": {
"account_number": "67070"
},
"Legal Fees": {
"account_number": "67080"
},
"Management Fees": {
"account_number": "67090"
},
"account_number": "670",
"is_group": 1
},
"Other Costs": {
"Doubtful Debts": {
"account_number": "67510"
},
"Fines": {
"account_number": "67520"
},
"Debt Collection": {
"account_number": "67530"
},
"Bad Debts": {
"account_number": "67540"
},
"account_number": "675",
"is_group": 1
},
"account_number": "67",
"is_group": 1
},
"Variable Expenses": {
"Variable Expenses": {
"Bonus & Commissions Paid": {
"account_number": "68010"
},
"Bonus & Commissions To be Paid": {
"account_number": "68020"
},
"Warranty Claims": {
"account_number": "68030"
},
"account_number": "680",
"is_group": 1
},
"account_number": "68",
"is_group": 1
},
"account_number": "6",
"root_type": "Expense"
},
"Other Income": {
"Other Income": {
"Interest Income": {
"Interest Income": {
"account_number": "71010"
},
"account_number": "710",
"is_group": 1
},
"Asset Disposal Income": {
"Gain on Asset Disposal": {
"account_number": "73010"
},
"account_number": "730",
"is_group": 1
},
"account_number": "71",
"is_group": 1
},
"account_number": "7",
"root_type": "Income"
},
"Other Expenses": {
"Other Expenses": {
"Income Tax Expenses": {
"Income Tax Expenses": {
"account_number": "81010"
},
"account_number": "810",
"is_group": 1
},
"Foreign Exchange Gain/Loss": {
"Exchange Loss/Gain - Realized": {
"account_number": "82010"
},
"account_number": "820",
"is_group": 1
},
"Asset Disposal Expenses": {
"Loss on Asset Disposal": {
"account_number": "83010"
},
"account_number": "830",
"is_group": 1
},
"account_number": "81",
"is_group": 1
},
"account_number": "8",
"root_type": "Expense"
}
}
}

View File

@@ -13,7 +13,7 @@ def get():
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1},
_("Cash In Hand"): {_("Cash"): {"account_type": "Cash"}, "account_type": "Cash"},
_("Loans and Advances (Assets)"): {
_("Employee Advances"): {},
_("Employee Advances"): {"account_type": "Payable"},
},
_("Securities and Deposits"): {_("Earnest Money"): {}},
_("Stock Assets"): {

View File

@@ -20,7 +20,7 @@ def get():
"account_number": "1100",
},
_("Loans and Advances (Assets)"): {
_("Employee Advances"): {"account_number": "1610"},
_("Employee Advances"): {"account_number": "1610", "account_type": "Payable"},
"account_number": "1600",
},
_("Securities and Deposits"): {

View File

@@ -111,17 +111,15 @@ class AccountingDimension(Document):
def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist:
doclist = get_doctypes_with_dimensions()
doc_count = len(get_accounting_dimensions())
count = 0
repostable_doctypes = get_allowed_types_from_settings()
repostable_doctypes = get_allowed_types_from_settings(child_doc=True)
for doctype in doclist:
if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break"
else:
insert_after_field = "accounting_dimensions_section"
df = {
"fieldname": doc.fieldname,
"label": doc.label,

View File

@@ -7,6 +7,7 @@
"engine": "InnoDB",
"field_order": [
"accounting_dimension",
"fieldname",
"disabled",
"column_break_2",
"company",
@@ -90,11 +91,17 @@
"fieldname": "apply_restriction_on_values",
"fieldtype": "Check",
"label": "Apply restriction on dimension values"
},
{
"fieldname": "fieldname",
"fieldtype": "Data",
"hidden": 1,
"label": "Fieldname"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-06-07 14:59:41.869117",
"modified": "2025-08-08 14:13:22.203011",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Dimension Filter",
@@ -139,8 +146,8 @@
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -17,17 +17,16 @@ class AccountingDimensionFilter(Document):
from frappe.types import DF
from erpnext.accounts.doctype.allowed_dimension.allowed_dimension import AllowedDimension
from erpnext.accounts.doctype.applicable_on_account.applicable_on_account import (
ApplicableOnAccount,
)
from erpnext.accounts.doctype.applicable_on_account.applicable_on_account import ApplicableOnAccount
accounting_dimension: DF.Literal
accounting_dimension: DF.Literal[None]
accounts: DF.Table[ApplicableOnAccount]
allow_or_restrict: DF.Literal["Allow", "Restrict"]
apply_restriction_on_values: DF.Check
company: DF.Link
dimensions: DF.Table[AllowedDimension]
disabled: DF.Check
fieldname: DF.Data | None
# end: auto-generated types
def before_save(self):
@@ -37,6 +36,10 @@ class AccountingDimensionFilter(Document):
self.set("dimensions", [])
def validate(self):
self.fieldname = frappe.db.get_value(
"Accounting Dimension", {"document_type": self.accounting_dimension}, "fieldname"
) or frappe.scrub(self.accounting_dimension) # scrub to handle default accounting dimension
self.validate_applicable_accounts()
def validate_applicable_accounts(self):
@@ -72,7 +75,7 @@ def get_dimension_filter_map():
"""
SELECT
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
p.allow_or_restrict, a.is_mandatory
p.allow_or_restrict, p.fieldname, a.is_mandatory
FROM
`tabApplicable On Account` a,
`tabAccounting Dimension Filter` p
@@ -87,8 +90,6 @@ def get_dimension_filter_map():
dimension_filter_map = {}
for f in filters:
f.fieldname = scrub(f.accounting_dimension)
build_map(
dimension_filter_map,
f.fieldname,

View File

@@ -26,9 +26,20 @@ frappe.ui.form.on("Accounts Settings", {
add_taxes_from_taxes_and_charges_template(frm) {
toggle_tax_settings(frm, "add_taxes_from_taxes_and_charges_template");
},
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

@@ -41,6 +41,7 @@
"show_payment_schedule_in_print",
"item_price_settings_section",
"maintain_same_internal_transaction_rate",
"fetch_valuation_rate_for_internal_transaction",
"column_break_feyo",
"maintain_same_rate_action",
"role_to_override_stop_action",
@@ -88,6 +89,8 @@
"receivable_payable_remarks_length",
"accounts_receivable_payable_tuning_section",
"receivable_payable_fetch_method",
"column_break_ntmi",
"drop_ar_procedures",
"legacy_section",
"ignore_is_opening_check_for_reporting",
"payment_request_settings",
@@ -552,7 +555,7 @@
"fieldname": "receivable_payable_fetch_method",
"fieldtype": "Select",
"label": "Data Fetch Method",
"options": "Buffered Cursor\nUnBuffered Cursor"
"options": "Buffered Cursor\nUnBuffered Cursor\nRaw SQL"
},
{
"fieldname": "accounts_receivable_payable_tuning_section",
@@ -609,6 +612,23 @@
"fieldname": "add_taxes_from_taxes_and_charges_template",
"fieldtype": "Check",
"label": "Automatically Add Taxes from Taxes and Charges Template"
},
{
"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",
"fieldtype": "Check",
"label": "Fetch Valuation Rate for Internal Transaction"
}
],
"icon": "icon-cog",
@@ -616,7 +636,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-06-23 15:55:33.346398",
"modified": "2025-07-18 13:56:47.192437",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -48,6 +48,7 @@ class AccountsSettings(Document):
enable_immutable_ledger: DF.Check
enable_party_matching: DF.Check
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
fetch_valuation_rate_for_internal_transaction: DF.Check
frozen_accounts_modifier: DF.Link | None
general_ledger_remarks_length: DF.Int
ignore_account_closing_balance: DF.Check
@@ -58,7 +59,7 @@ class AccountsSettings(Document):
merge_similar_account_heads: DF.Check
over_billing_allowance: DF.Currency
post_change_gl_entries: DF.Check
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor", "Raw SQL"]
receivable_payable_remarks_length: DF.Int
reconciliation_queue_size: DF.Int
role_allowed_to_over_bill: DF.Link | None
@@ -152,3 +153,11 @@ class AccountsSettings(Document):
),
title=_("Auto Tax Settings Error"),
)
@frappe.whitelist()
def drop_ar_sql_procedures(self):
from erpnext.accounts.report.accounts_receivable.accounts_receivable import InitSQLProceduresForAR
frappe.db.sql(f"drop function if exists {InitSQLProceduresForAR.genkey_function_name}")
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")

View File

@@ -12,7 +12,8 @@
"against_voucher_no",
"amount",
"currency",
"event"
"event",
"delinked"
],
"fields": [
{
@@ -68,12 +69,20 @@
"label": "Company",
"options": "Company",
"read_only": 1
},
{
"default": "0",
"fieldname": "delinked",
"fieldtype": "Check",
"label": "DeLinked",
"read_only": 1
}
],
"grid_page_length": 50,
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-11-05 10:31:28.736671",
"modified": "2025-07-29 11:37:42.678556",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Payment Ledger Entry",
@@ -107,7 +116,8 @@
"share": 1
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -1,9 +1,11 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
import frappe
from frappe.model.document import Document
from erpnext.accounts.utils import get_advance_payment_doctypes, update_voucher_outstanding
class AdvancePaymentLedgerEntry(Document):
# begin: auto-generated types
@@ -19,9 +21,16 @@ class AdvancePaymentLedgerEntry(Document):
amount: DF.Currency
company: DF.Link | None
currency: DF.Link | None
delinked: DF.Check
event: DF.Data | None
voucher_no: DF.DynamicLink | None
voucher_type: DF.Link | None
# end: auto-generated types
pass
def on_update(self):
if (
self.against_voucher_type in get_advance_payment_doctypes()
and self.flags.update_outstanding == "Yes"
and not frappe.flags.is_reverse_depr_entry
):
update_voucher_outstanding(self.against_voucher_type, self.against_voucher_no, None, None, None)

View File

@@ -109,6 +109,7 @@ class BankAccount(Document):
"party_type": self.party_type,
"party": self.party,
"is_company_account": self.is_company_account,
"company": self.company,
"is_default": 1,
"disabled": 0,
},

View File

@@ -9,7 +9,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Sum
from frappe.utils import cint, flt
from frappe.utils import cint, create_batch, flt
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
@@ -377,16 +377,17 @@ def auto_reconcile_vouchers(
bank_transactions = get_bank_transactions(bank_account)
if len(bank_transactions) > 10:
frappe.enqueue(
method="erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.start_auto_reconcile",
queue="long",
bank_transactions=bank_transactions,
from_date=from_date,
to_date=to_date,
filter_by_reference_date=filter_by_reference_date,
from_reference_date=from_reference_date,
to_reference_date=to_reference_date,
)
for bank_transaction_batch in create_batch(bank_transactions, 1000):
frappe.enqueue(
method="erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.start_auto_reconcile",
queue="long",
bank_transactions=bank_transaction_batch,
from_date=from_date,
to_date=to_date,
filter_by_reference_date=filter_by_reference_date,
from_reference_date=from_reference_date,
to_reference_date=to_reference_date,
)
frappe.msgprint(_("Auto Reconciliation has started in the background"))
else:
start_auto_reconcile(

View File

@@ -462,9 +462,8 @@ def unset_existing_data(company):
"Sales Taxes and Charges Template",
"Purchase Taxes and Charges Template",
]:
frappe.db.sql(
f'''delete from `tab{doctype}` where `company`="%s"''' % (company) # nosec
)
dt = frappe.qb.DocType(doctype)
frappe.qb.from_(dt).where(dt.company == company).delete().run()
def set_default_accounts(company):

View File

@@ -4,6 +4,8 @@
import unittest
import frappe
from frappe.query_builder.functions import Sum
from frappe.tests.utils import change_settings
from frappe.utils import add_days, today
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
@@ -190,6 +192,31 @@ class TestCostCenterAllocation(unittest.TestCase):
coa2.cancel()
jv.cancel()
@change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
def test_debit_credit_on_cost_center_allocation_for_commercial_rounding(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
cca = create_cost_center_allocation(
"_Test Company",
"Main Cost Center 1 - _TC",
{"Sub Cost Center 2 - _TC": 50, "Sub Cost Center 3 - _TC": 50},
)
si = create_sales_invoice(rate=145.65, cost_center="Main Cost Center 1 - _TC")
gl_entry = frappe.qb.DocType("GL Entry")
gl_entries = (
frappe.qb.from_(gl_entry)
.select(Sum(gl_entry.credit).as_("cr"), Sum(gl_entry.debit).as_("dr"))
.where(gl_entry.voucher_type == "Sales Invoice")
.where(gl_entry.voucher_no == si.name)
).run(as_dict=1)
self.assertEqual(gl_entries[0].cr, gl_entries[0].dr)
si.cancel()
cca.cancel()
def create_cost_center_allocation(
company,

View File

@@ -11,6 +11,7 @@
-> Resolves dunning automatically
"""
import json
import frappe
@@ -156,40 +157,66 @@ class Dunning(AccountsController):
]
def resolve_dunning(doc, state):
"""
Check if all payments have been made and resolve dunning, if yes. Called
when a Payment Entry is submitted.
"""
for reference in doc.references:
# Consider partial and full payments:
# Submitting full payment: outstanding_amount will be 0
# Submitting 1st partial payment: outstanding_amount will be the pending installment
# Cancelling full payment: outstanding_amount will revert to total amount
# Cancelling last partial payment: outstanding_amount will revert to pending amount
submit_condition = reference.outstanding_amount < reference.total_amount
cancel_condition = reference.outstanding_amount <= reference.total_amount
def update_linked_dunnings(doc, previous_outstanding_amount):
if (
doc.doctype != "Sales Invoice"
or doc.is_return
or previous_outstanding_amount == doc.outstanding_amount
):
return
if reference.reference_doctype == "Sales Invoice" and (
submit_condition if doc.docstatus == 1 else cancel_condition
):
state = "Resolved" if doc.docstatus == 2 else "Unresolved"
dunnings = get_linked_dunnings_as_per_state(reference.reference_name, state)
to_resolve = doc.outstanding_amount < previous_outstanding_amount
state = "Unresolved" if to_resolve else "Resolved"
dunnings = get_linked_dunnings_as_per_state(doc.name, state)
if not dunnings:
return
for dunning in dunnings:
resolve = True
dunning = frappe.get_doc("Dunning", dunning.get("name"))
for overdue_payment in dunning.overdue_payments:
outstanding_inv = frappe.get_value(
"Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount"
)
outstanding_ps = frappe.get_value(
"Payment Schedule", overdue_payment.payment_schedule, "outstanding"
)
resolve = False if (outstanding_ps > 0 and outstanding_inv > 0) else True
dunnings = [frappe.get_doc("Dunning", dunning.name) for dunning in dunnings]
invoices = set()
payment_schedule_ids = set()
dunning.status = "Resolved" if resolve else "Unresolved"
dunning.save()
for dunning in dunnings:
for overdue_payment in dunning.overdue_payments:
invoices.add(overdue_payment.sales_invoice)
if overdue_payment.payment_schedule:
payment_schedule_ids.add(overdue_payment.payment_schedule)
invoice_outstanding_amounts = dict(
frappe.get_all(
"Sales Invoice",
filters={"name": ["in", list(invoices)]},
fields=["name", "outstanding_amount"],
as_list=True,
)
)
ps_outstanding_amounts = (
dict(
frappe.get_all(
"Payment Schedule",
filters={"name": ["in", list(payment_schedule_ids)]},
fields=["name", "outstanding"],
as_list=True,
)
)
if payment_schedule_ids
else {}
)
for dunning in dunnings:
has_outstanding = False
for overdue_payment in dunning.overdue_payments:
invoice_outstanding = invoice_outstanding_amounts[overdue_payment.sales_invoice]
ps_outstanding = ps_outstanding_amounts.get(overdue_payment.payment_schedule, 0)
has_outstanding = invoice_outstanding > 0 and ps_outstanding > 0
if has_outstanding:
break
new_status = "Resolved" if not has_outstanding else "Unresolved"
if dunning.status != new_status:
dunning.status = new_status
dunning.save()
def get_linked_dunnings_as_per_state(sales_invoice, state):

View File

@@ -139,6 +139,64 @@ class TestDunning(FrappeTestCase):
self.assertEqual(sales_invoice.status, "Overdue")
self.assertEqual(dunning.status, "Unresolved")
def test_dunning_resolution_from_credit_note(self):
"""
Test that dunning is resolved when a credit note is issued against the original invoice.
"""
sales_invoice = create_sales_invoice_against_cost_center(
posting_date=add_days(today(), -10), qty=1, rate=100
)
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
dunning.submit()
self.assertEqual(dunning.status, "Unresolved")
credit_note = frappe.copy_doc(sales_invoice)
credit_note.is_return = 1
credit_note.return_against = sales_invoice.name
credit_note.update_outstanding_for_self = 0
for item in credit_note.items:
item.qty = -item.qty
credit_note.save()
credit_note.submit()
dunning.reload()
self.assertEqual(dunning.status, "Resolved")
credit_note.cancel()
dunning.reload()
self.assertEqual(dunning.status, "Unresolved")
def test_dunning_not_affected_by_standalone_credit_note(self):
"""
Test that dunning is NOT resolved when a credit note has update_outstanding_for_self checked.
"""
sales_invoice = create_sales_invoice_against_cost_center(
posting_date=add_days(today(), -10), qty=1, rate=100
)
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
dunning.submit()
self.assertEqual(dunning.status, "Unresolved")
credit_note = frappe.copy_doc(sales_invoice)
credit_note.is_return = 1
credit_note.return_against = sales_invoice.name
credit_note.update_outstanding_for_self = 1
for item in credit_note.items:
item.qty = -item.qty
credit_note.save()
credit_note = frappe.get_doc("Sales Invoice", credit_note.name)
credit_note.submit()
dunning.reload()
self.assertEqual(dunning.status, "Unresolved")
def create_dunning(overdue_days, dunning_type_name=None):
posting_date = add_days(today(), -1 * overdue_days)

View File

@@ -462,4 +462,9 @@ def rename_temporarily_named_docs(doctype):
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
(newname, now(), oldname),
)
for hook_type in ("on_gle_rename", "on_sle_rename"):
for hook in frappe.get_hooks(hook_type):
frappe.call(hook, newname=newname, oldname=oldname)
frappe.db.commit()

View File

@@ -163,6 +163,7 @@ frappe.ui.form.on("Journal Entry", {
});
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
erpnext.utils.set_letter_head(frm);
},
voucher_type: function (frm) {

View File

@@ -191,8 +191,6 @@ class JournalEntry(AccountsController):
self.validate_cheque_info()
self.check_credit_limit()
self.make_gl_entries()
self.make_advance_payment_ledger_entries()
self.update_advance_paid()
self.update_asset_value()
self.update_inter_company_jv()
self.update_invoice_discounting()
@@ -225,8 +223,6 @@ class JournalEntry(AccountsController):
"Advance Payment Ledger Entry",
)
self.make_gl_entries(1)
self.make_advance_payment_ledger_entries()
self.update_advance_paid()
self.unlink_advance_entry_reference()
self.unlink_asset_reference()
self.unlink_inter_company_jv()
@@ -237,18 +233,6 @@ class JournalEntry(AccountsController):
def get_title(self):
return self.pay_to_recd_from or self.accounts[0].account
def update_advance_paid(self):
advance_paid = frappe._dict()
advance_payment_doctypes = get_advance_payment_doctypes()
for d in self.get("accounts"):
if d.is_advance:
if d.reference_type in advance_payment_doctypes:
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
for voucher_type, order_list in advance_paid.items():
for voucher_no in list(set(order_list)):
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
def validate_inter_company_accounts(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
doc = frappe.db.get_value(
@@ -568,6 +552,8 @@ class JournalEntry(AccountsController):
elif (
d.party_type
and frappe.db.get_value("Party Type", d.party_type, "account_type") != account_type
and d.party_type
!= "Employee" # making an excpetion for employee since they can be both payable and receivable
):
frappe.throw(
_("Row {0}: Account {1} and Party Type {2} have different account types").format(
@@ -1044,9 +1030,7 @@ class JournalEntry(AccountsController):
def set_print_format_fields(self):
bank_amount = party_amount = total_amount = 0.0
currency = (
bank_account_currency
) = party_account_currency = pay_to_recd_from = self.pay_to_recd_from = None
currency = bank_account_currency = party_account_currency = pay_to_recd_from = None
party_type = None
for d in self.get("accounts"):
if d.party_type in ["Customer", "Supplier"] and d.party:
@@ -1096,49 +1080,65 @@ class JournalEntry(AccountsController):
self.transaction_exchange_rate = row.exchange_rate
break
advance_doctypes = get_advance_payment_doctypes()
for d in self.get("accounts"):
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
r = [d.user_remark, self.remark]
r = [x for x in r if x]
remarks = "\n".join(r)
row = {
"account": d.account,
"party_type": d.party_type,
"due_date": self.due_date,
"party": d.party,
"against": d.against_account,
"debit": flt(d.debit, d.precision("debit")),
"credit": flt(d.credit, d.precision("credit")),
"account_currency": d.account_currency,
"debit_in_account_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
),
"credit_in_account_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
),
"transaction_currency": self.transaction_currency,
"transaction_exchange_rate": self.transaction_exchange_rate,
"debit_in_transaction_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
"credit_in_transaction_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
"against_voucher_type": d.reference_type,
"against_voucher": d.reference_name,
"remarks": remarks,
"voucher_detail_no": d.reference_detail_no,
"cost_center": d.cost_center,
"project": d.project,
"finance_book": self.finance_book,
"advance_voucher_type": d.advance_voucher_type,
"advance_voucher_no": d.advance_voucher_no,
}
if d.reference_type in advance_doctypes:
row.update(
{
"against_voucher_type": self.doctype,
"against_voucher": self.name,
"advance_voucher_type": d.reference_type,
"advance_voucher_no": d.reference_name,
}
)
gl_map.append(
self.get_gl_dict(
{
"account": d.account,
"party_type": d.party_type,
"due_date": self.due_date,
"party": d.party,
"against": d.against_account,
"debit": flt(d.debit, d.precision("debit")),
"credit": flt(d.credit, d.precision("credit")),
"account_currency": d.account_currency,
"debit_in_account_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
),
"credit_in_account_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
),
"transaction_currency": self.transaction_currency,
"transaction_exchange_rate": self.transaction_exchange_rate,
"debit_in_transaction_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
"credit_in_transaction_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
"against_voucher_type": d.reference_type,
"against_voucher": d.reference_name,
"remarks": remarks,
"voucher_detail_no": d.reference_detail_no,
"cost_center": d.cost_center,
"project": d.project,
"finance_book": self.finance_book,
},
row,
item=d,
)
)

View File

@@ -580,6 +580,18 @@ class TestJournalEntry(unittest.TestCase):
]
self.assertEqual(expected, actual)
def test_pay_to_recd_from(self):
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
jv.pay_to_recd_from = "_Test Receiver"
jv.save()
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver")
jv.pay_to_recd_from = "_Test Receiver 2"
jv.save()
jv.submit()
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver 2")
def make_journal_entry(
account1,

View File

@@ -32,6 +32,8 @@
"reference_name",
"reference_due_date",
"reference_detail_no",
"advance_voucher_type",
"advance_voucher_no",
"col_break3",
"is_advance",
"user_remark",
@@ -263,12 +265,28 @@
"label": "Reference Detail No",
"no_copy": 1,
"search_index": 1
},
{
"fieldname": "advance_voucher_type",
"fieldtype": "Link",
"label": "Advance Voucher Type",
"no_copy": 1,
"options": "DocType",
"read_only": 1
},
{
"fieldname": "advance_voucher_no",
"fieldtype": "Dynamic Link",
"label": "Advance Voucher No",
"no_copy": 1,
"options": "advance_voucher_type",
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-02-05 01:10:50.224840",
"modified": "2025-07-25 04:45:28.117715",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",
@@ -279,4 +297,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -17,8 +17,9 @@ class JournalEntryAccount(Document):
account: DF.Link
account_currency: DF.Link | None
account_type: DF.Data | None
advance_voucher_no: DF.DynamicLink | None
advance_voucher_type: DF.Link | None
against_account: DF.Text | None
balance: DF.Currency
bank_account: DF.Link | None
cost_center: DF.Link | None
credit: DF.Currency
@@ -31,7 +32,6 @@ class JournalEntryAccount(Document):
parentfield: DF.Data
parenttype: DF.Data
party: DF.DynamicLink | None
party_balance: DF.Currency
party_type: DF.Link | None
project: DF.Link | None
reference_detail_no: DF.Data | None

View File

@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import flt, today
@@ -45,22 +46,30 @@ def get_loyalty_details(
if not expiry_date:
expiry_date = today()
condition = ""
if company:
condition = " and company=%s " % frappe.db.escape(company)
if not include_expired_entry:
condition += " and expiry_date>='%s' " % expiry_date
LoyaltyPointEntry = frappe.qb.DocType("Loyalty Point Entry")
loyalty_point_details = frappe.db.sql(
f"""select sum(loyalty_points) as loyalty_points,
sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s and posting_date <= %s
{condition}
group by customer""",
(customer, loyalty_program, expiry_date),
as_dict=1,
query = (
frappe.qb.from_(LoyaltyPointEntry)
.select(
Sum(LoyaltyPointEntry.loyalty_points).as_("loyalty_points"),
Sum(LoyaltyPointEntry.purchase_amount).as_("total_spent"),
)
.where(
(LoyaltyPointEntry.customer == customer)
& (LoyaltyPointEntry.loyalty_program == loyalty_program)
& (LoyaltyPointEntry.posting_date <= expiry_date)
)
.groupby(LoyaltyPointEntry.customer)
)
if company:
query = query.where(LoyaltyPointEntry.company == company)
if not include_expired_entry:
query = query.where(LoyaltyPointEntry.expiry_date >= expiry_date)
loyalty_point_details = query.run(as_dict=True)
if loyalty_point_details:
return loyalty_point_details[0]
else:

View File

@@ -273,6 +273,7 @@ frappe.ui.form.on("Payment Entry", {
frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm);
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
erpnext.utils.set_letter_head(frm);
},
contact_person: function (frm) {

View File

@@ -117,12 +117,10 @@ class PaymentEntry(AccountsController):
def on_submit(self):
if self.difference_amount:
frappe.throw(_("Difference Amount must be zero"))
self.update_payment_requests()
self.update_payment_schedule()
self.make_gl_entries()
self.update_outstanding_amounts()
self.update_payment_schedule()
self.update_payment_requests()
self.make_advance_payment_ledger_entries()
self.update_advance_paid() # advance_paid_status depends on the payment request amount
self.set_status()
def validate_for_repost(self):
@@ -222,13 +220,11 @@ class PaymentEntry(AccountsController):
"Advance Payment Ledger Entry",
)
super().on_cancel()
self.update_payment_requests(cancel=True)
self.update_payment_schedule(cancel=1)
self.make_gl_entries(cancel=1)
self.update_outstanding_amounts()
self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1)
self.update_payment_requests(cancel=True)
self.make_advance_payment_ledger_entries()
self.update_advance_paid() # advance_paid_status depends on the payment request amount
self.set_status()
def update_payment_requests(self, cancel=False):
@@ -1366,23 +1362,27 @@ class PaymentEntry(AccountsController):
dr_or_cr + "_in_transaction_currency": d.allocated_amount
if self.transaction_currency == self.party_account_currency
else allocated_amount_in_company_currency / self.transaction_exchange_rate,
"advance_voucher_type": d.advance_voucher_type,
"advance_voucher_no": d.advance_voucher_no,
},
item=self,
)
)
if self.book_advance_payments_in_separate_party_account:
if d.reference_doctype in advance_payment_doctypes:
# Upon reconciliation, whole ledger will be reposted. So, reference to SO/PO is fine
gle.update(
{
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
}
)
else:
# Do not reference Invoices while Advance is in separate party account
gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name})
if d.reference_doctype in advance_payment_doctypes:
# advance reference
gle.update(
{
"against_voucher_type": self.doctype,
"against_voucher": self.name,
"advance_voucher_type": d.reference_doctype,
"advance_voucher_no": d.reference_name,
}
)
elif self.book_advance_payments_in_separate_party_account:
# Do not reference Invoices while Advance is in separate party account
gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name})
else:
gle.update(
{
@@ -1487,13 +1487,14 @@ class PaymentEntry(AccountsController):
"voucher_no": self.name,
"voucher_detail_no": invoice.name,
}
if invoice.reconcile_effect_on:
posting_date = invoice.reconcile_effect_on
else:
# For backwards compatibility
# Supporting reposting on payment entries reconciled before select field introduction
posting_date = get_reconciliation_effect_date(invoice, self.company, self.posting_date)
posting_date = get_reconciliation_effect_date(
invoice.reference_doctype, invoice.reference_name, self.company, self.posting_date
)
frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
@@ -1511,6 +1512,8 @@ class PaymentEntry(AccountsController):
{
"against_voucher_type": invoice.reference_doctype,
"against_voucher": invoice.reference_name,
"advance_voucher_type": invoice.advance_voucher_type,
"advance_voucher_no": invoice.advance_voucher_no,
"posting_date": posting_date,
}
)
@@ -1535,6 +1538,8 @@ class PaymentEntry(AccountsController):
{
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
"advance_voucher_type": invoice.advance_voucher_type,
"advance_voucher_no": invoice.advance_voucher_no,
}
)
gle = self.get_gl_dict(
@@ -1683,17 +1688,6 @@ class PaymentEntry(AccountsController):
return flt(gl_dict.get(field, 0) / (conversion_rate or 1))
def update_advance_paid(self):
if self.payment_type not in ("Receive", "Pay") or not self.party:
return
advance_payment_doctypes = get_advance_payment_doctypes()
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
frappe.get_doc(
d.reference_doctype, d.reference_name, for_update=True
).set_total_advance_paid()
def on_recurring(self, reference_doc, auto_repeat_doc):
self.reference_no = reference_doc.name
self.reference_date = nowdate()

View File

@@ -52,7 +52,7 @@ class TestPaymentEntry(FrappeTestCase):
self.assertEqual(pe.paid_to_account_type, "Cash")
expected_gle = dict(
(d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]]
(d[0], d) for d in [["Debtors - _TC", 0, 1000, pe.name], ["_Test Cash - _TC", 1000.0, 0, None]]
)
self.validate_gl_entries(pe.name, expected_gle)
@@ -84,7 +84,7 @@ class TestPaymentEntry(FrappeTestCase):
expected_gle = dict(
(d[0], d)
for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]]
for d in [["_Test Receivable USD - _TC", 0, 5500, pe.name], [pe.paid_to, 5500.0, 0, None]]
)
self.validate_gl_entries(pe.name, expected_gle)

View File

@@ -22,7 +22,9 @@
"exchange_gain_loss",
"account",
"payment_request",
"payment_request_outstanding"
"payment_request_outstanding",
"advance_voucher_type",
"advance_voucher_no"
],
"fields": [
{
@@ -151,12 +153,28 @@
"fieldtype": "Date",
"label": "Reconcile Effect On",
"read_only": 1
},
{
"columns": 2,
"fieldname": "advance_voucher_type",
"fieldtype": "Link",
"label": "Advance Voucher Type",
"options": "DocType",
"read_only": 1
},
{
"columns": 2,
"fieldname": "advance_voucher_no",
"fieldtype": "Dynamic Link",
"label": "Advance Voucher No",
"options": "advance_voucher_type",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-01-13 15:56:18.895082",
"modified": "2025-07-25 04:32:11.040025",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
@@ -167,4 +185,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -16,6 +16,8 @@ class PaymentEntryReference(Document):
account: DF.Link | None
account_type: DF.Data | None
advance_voucher_no: DF.DynamicLink | None
advance_voucher_type: DF.Link | None
allocated_amount: DF.Float
bill_no: DF.Data | None
due_date: DF.Date | None
@@ -26,7 +28,6 @@ class PaymentEntryReference(Document):
parentfield: DF.Data
parenttype: DF.Data
payment_request: DF.Link | None
payment_request_outstanding: DF.Float
payment_term: DF.Link | None
payment_term_outstanding: DF.Float
payment_type: DF.Data | None

View File

@@ -8,4 +8,14 @@ frappe.ui.form.on("Payment Gateway Account", {
frm.set_df_property("payment_gateway", "read_only", 1);
}
},
setup(frm) {
frm.set_query("payment_account", function () {
return {
filters: {
company: frm.doc.company,
},
};
});
},
});

View File

@@ -6,6 +6,7 @@
"field_order": [
"payment_gateway",
"payment_channel",
"company",
"is_default",
"column_break_4",
"payment_account",
@@ -70,11 +71,21 @@
"fieldtype": "Select",
"label": "Payment Channel",
"options": "\nEmail\nPhone"
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"print_hide": 1,
"remember_last_selected_value": 1,
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-09-20 13:30:27.722852",
"modified": "2025-07-14 16:49:55.210352",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Gateway Account",
@@ -95,4 +106,4 @@
],
"sort_field": "modified",
"sort_order": "DESC"
}
}

View File

@@ -15,6 +15,7 @@ class PaymentGatewayAccount(Document):
if TYPE_CHECKING:
from frappe.types import DF
company: DF.Link
currency: DF.ReadOnly | None
is_default: DF.Check
message: DF.SmallText | None
@@ -24,7 +25,8 @@ class PaymentGatewayAccount(Document):
# end: auto-generated types
def autoname(self):
self.name = self.payment_gateway + " - " + self.currency
abbr = frappe.db.get_value("Company", self.company, "abbr")
self.name = self.payment_gateway + " - " + self.currency + " - " + abbr
def validate(self):
self.currency = frappe.get_cached_value("Account", self.payment_account, "account_currency")
@@ -34,13 +36,15 @@ class PaymentGatewayAccount(Document):
def update_default_payment_gateway(self):
if self.is_default:
frappe.db.sql(
"""update `tabPayment Gateway Account` set is_default = 0
where is_default = 1 """
frappe.db.set_value(
"Payment Gateway Account",
{"is_default": 1, "name": ["!=", self.name], "company": self.company},
"is_default",
0,
)
def set_as_default_if_not_set(self):
if not frappe.db.get_value(
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name)}, "name"
if not frappe.db.exists(
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name), "company": self.company}
):
self.is_default = 1

View File

@@ -197,4 +197,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -1714,6 +1714,67 @@ class TestPaymentReconciliation(FrappeTestCase):
)
self.assertEqual(len(pl_entries), 3)
def test_advance_payment_reconciliation_date_for_older_date(self):
old_settings = frappe.db.get_value(
"Company",
self.company,
[
"reconciliation_takes_effect_on",
"default_advance_paid_account",
"book_advance_payments_in_separate_party_account",
],
as_dict=True,
)
frappe.db.set_value(
"Company",
self.company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": self.advance_payable_account,
"reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance",
},
)
self.supplier = "_Test Supplier"
pi1 = self.create_purchase_invoice(qty=10, rate=100)
po = self.create_purchase_order(qty=10, rate=100)
pay = get_payment_entry(po.doctype, po.name)
pay.paid_amount = 1000
pay.save().submit()
pr = frappe.new_doc("Payment Reconciliation")
pr.company = self.company
pr.party_type = "Supplier"
pr.party = self.supplier
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
pr.default_advance_account = self.advance_payable_account
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.allocation[0].allocated_amount = 100
pr.reconcile()
pay.reload()
self.assertEqual(getdate(pay.references[0].reconcile_effect_on), getdate(pi1.posting_date))
# test setting of date if not available
frappe.db.set_value("Payment Entry Reference", pay.references[1].name, "reconcile_effect_on", None)
pay.reload()
pay.cancel()
pay.reload()
pi1.reload()
po.reload()
self.assertEqual(getdate(pay.references[0].reconcile_effect_on), getdate(pi1.posting_date))
pi1.cancel()
po.cancel()
frappe.db.set_value("Company", self.company, old_settings)
def test_advance_payment_reconciliation_against_journal_for_customer(self):
frappe.db.set_value(
"Company",
@@ -2145,6 +2206,136 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(len(pr.get("payments")), 0)
self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 200)
def test_partial_advance_payment_with_closed_fiscal_year(self):
"""
Test Advance Payment partial reconciliation before period closing and partial after period closing
"""
default_settings = frappe.db.get_value(
"Company",
self.company,
[
"book_advance_payments_in_separate_party_account",
"default_advance_paid_account",
"reconciliation_takes_effect_on",
],
as_dict=True,
)
first_fy_start_date = frappe.db.get_value("Fiscal Year", {"disabled": 0}, "min(year_start_date)")
prev_fy_start_date = add_years(first_fy_start_date, -1)
prev_fy_end_date = add_days(first_fy_start_date, -1)
create_fiscal_year(
company=self.company, year_start_date=prev_fy_start_date, year_end_date=prev_fy_end_date
)
frappe.db.set_value(
"Company",
self.company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": self.advance_payable_account,
"reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance",
},
)
self.supplier = "_Test Supplier"
# Create advance payment of 1000 (previous FY)
pe = self.create_payment_entry(amount=1000, posting_date=prev_fy_start_date)
pe.party_type = "Supplier"
pe.party = self.supplier
pe.payment_type = "Pay"
pe.paid_from = self.cash
pe.paid_to = self.advance_payable_account
pe.save().submit()
# Create purchase invoice of 600 (previous FY)
pi1 = self.create_purchase_invoice(qty=1, rate=600, do_not_submit=True)
pi1.posting_date = prev_fy_start_date
pi1.set_posting_time = 1
pi1.supplier = self.supplier
pi1.credit_to = self.creditors
pi1.save().submit()
# Reconcile advance payment
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.party = self.supplier
pr.receivable_payable_account = self.creditors
pr.default_advance_account = self.advance_payable_account
pr.from_invoice_date = pr.to_invoice_date = pi1.posting_date
pr.from_payment_date = pr.to_payment_date = pe.posting_date
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.invoices if x.invoice_number == pi1.name]
payments = [x.as_dict() for x in pr.payments if x.reference_name == pe.name]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
# Verify partial reconciliation
pe.reload()
pi1.reload()
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.references[0].allocated_amount, 600)
self.assertEqual(flt(pe.unallocated_amount), 400)
self.assertEqual(pi1.outstanding_amount, 0)
self.assertEqual(pi1.status, "Paid")
# Close accounting period for March (previous FY)
pcv = make_period_closing_voucher(
company=self.company, cost_center=self.cost_center, posting_date=prev_fy_end_date
)
pcv.reload()
self.assertEqual(pcv.gle_processing_status, "Completed")
# Change reconciliation setting to "Reconciliation Date"
frappe.db.set_value(
"Company",
self.company,
"reconciliation_takes_effect_on",
"Reconciliation Date",
)
# Create new purchase invoice for 400 in new fiscal year
pi2 = self.create_purchase_invoice(qty=1, rate=400, do_not_submit=True)
pi2.posting_date = today()
pi2.set_posting_time = 1
pi2.supplier = self.supplier
pi2.currency = "INR"
pi2.credit_to = self.creditors
pi2.save()
pi2.submit()
# Allocate 600 from advance payment to purchase invoice
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.party = self.supplier
pr.receivable_payable_account = self.creditors
pr.default_advance_account = self.advance_payable_account
pr.from_invoice_date = pr.to_invoice_date = pi2.posting_date
pr.from_payment_date = pr.to_payment_date = pe.posting_date
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.invoices if x.invoice_number == pi2.name]
payments = [x.as_dict() for x in pr.payments if x.reference_name == pe.name]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pe.reload()
pi2.reload()
# Assert advance payment is fully allocated
self.assertEqual(len(pe.references), 2)
self.assertEqual(flt(pe.unallocated_amount), 0)
# Assert new invoice is fully paid
self.assertEqual(pi2.outstanding_amount, 0)
self.assertEqual(pi2.status, "Paid")
# Verify reconciliation dates are correct based on company setting
self.assertEqual(getdate(pe.references[0].reconcile_effect_on), getdate(pi1.posting_date))
self.assertEqual(getdate(pe.references[1].reconcile_effect_on), getdate(pi2.posting_date))
frappe.db.set_value("Company", self.company, default_settings)
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -9,6 +9,14 @@ frappe.ui.form.on("Payment Request", {
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
};
});
frm.set_query("payment_gateway_account", function () {
return {
filters: {
company: frm.doc.company,
},
};
});
},
});

View File

@@ -539,7 +539,9 @@ def make_payment_request(**args):
if args.dt not in ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST:
frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt)))
ref_doc = frappe.get_doc(args.dt, args.dn)
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
if not args.get("company"):
args.company = ref_doc.company
gateway_account = get_gateway_details(args) or frappe._dict()
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
@@ -782,7 +784,7 @@ def get_gateway_details(args): # nosemgrep
"""
Return gateway and payment account of default payment gateway
"""
gateway_account = args.get("payment_gateway_account", {"is_default": 1})
gateway_account = args.get("payment_gateway_account", {"is_default": 1, "company": args.company})
if gateway_account:
return get_payment_gateway_account(gateway_account)

View File

@@ -28,12 +28,14 @@ payment_method = [
"payment_gateway": "_Test Gateway",
"payment_account": "_Test Bank - _TC",
"currency": "INR",
"company": "_Test Company",
},
{
"doctype": "Payment Gateway Account",
"payment_gateway": "_Test Gateway",
"payment_account": "_Test Bank USD - _TC",
"currency": "USD",
"company": "_Test Company",
},
]
@@ -46,7 +48,11 @@ class TestPaymentRequest(FrappeTestCase):
for method in payment_method:
if not frappe.db.get_value(
"Payment Gateway Account",
{"payment_gateway": method["payment_gateway"], "currency": method["currency"]},
{
"payment_gateway": method["payment_gateway"],
"currency": method["currency"],
"company": method["company"],
},
"name",
):
frappe.get_doc(method).insert(ignore_permissions=True)
@@ -60,7 +66,7 @@ class TestPaymentRequest(FrappeTestCase):
dt="Sales Order",
dn=so_inr.name,
recipient_id="saurabh@erpnext.com",
payment_gateway_account="_Test Gateway - INR",
payment_gateway_account="_Test Gateway - INR - _TC",
)
self.assertEqual(pr.reference_doctype, "Sales Order")
@@ -74,7 +80,7 @@ class TestPaymentRequest(FrappeTestCase):
dt="Sales Invoice",
dn=si_usd.name,
recipient_id="saurabh@erpnext.com",
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
)
self.assertEqual(pr.reference_doctype, "Sales Invoice")
@@ -95,7 +101,7 @@ class TestPaymentRequest(FrappeTestCase):
party="_Test Supplier USD",
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
submit_doc=1,
return_doc=1,
)
@@ -119,7 +125,7 @@ class TestPaymentRequest(FrappeTestCase):
dn=purchase_invoice.name,
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
return_doc=1,
)
@@ -138,7 +144,7 @@ class TestPaymentRequest(FrappeTestCase):
dn=purchase_invoice.name,
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
return_doc=1,
)
@@ -162,7 +168,7 @@ class TestPaymentRequest(FrappeTestCase):
dn=so_inr.name,
recipient_id="saurabh@erpnext.com",
mute_email=1,
payment_gateway_account="_Test Gateway - INR",
payment_gateway_account="_Test Gateway - INR - _TC",
submit_doc=1,
return_doc=1,
)
@@ -184,7 +190,7 @@ class TestPaymentRequest(FrappeTestCase):
dn=si_usd.name,
recipient_id="saurabh@erpnext.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
submit_doc=1,
return_doc=1,
)
@@ -228,7 +234,7 @@ class TestPaymentRequest(FrappeTestCase):
dn=si_usd.name,
recipient_id="saurabh@erpnext.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
submit_doc=1,
return_doc=1,
)
@@ -328,7 +334,7 @@ class TestPaymentRequest(FrappeTestCase):
self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount
self.assertEqual(pe.references[0].allocated_amount, 800)
self.assertEqual(pe.references[0].outstanding_amount, 800) # for Orders it is not zero
self.assertEqual(pe.references[0].outstanding_amount, 0) # Also for orders it will zero
self.assertEqual(pe.references[0].payment_request, pr.name)
so.load_from_db()

View File

@@ -210,8 +210,10 @@ class PeriodClosingVoucher(AccountsController):
return gl_entry
def get_gle_for_closing_account(self, dimension_balance, dimensions):
balance_in_account_currency = flt(dimension_balance.balance_in_account_currency)
balance_in_company_currency = flt(dimension_balance.balance_in_company_currency)
debit = balance_in_company_currency if balance_in_company_currency > 0 else 0
credit = abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0
gl_entry = frappe._dict(
{
"company": self.company,
@@ -220,14 +222,10 @@ class PeriodClosingVoucher(AccountsController):
"account_currency": frappe.db.get_value(
"Account", self.closing_account_head, "account_currency"
),
"debit_in_account_currency": balance_in_account_currency
if balance_in_account_currency > 0
else 0,
"debit": balance_in_company_currency if balance_in_company_currency > 0 else 0,
"credit_in_account_currency": abs(balance_in_account_currency)
if balance_in_account_currency < 0
else 0,
"credit": abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0,
"debit_in_account_currency": debit,
"debit": debit,
"credit_in_account_currency": credit,
"credit": credit,
"is_period_closing_voucher_entry": 1,
"voucher_type": "Period Closing Voucher",
"voucher_no": self.name,

View File

@@ -14,6 +14,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
}
company() {
erpnext.utils.set_letter_head(this.frm);
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
this.frm.set_value("set_warehouse", "");
this.frm.set_value("taxes_and_charges", "");
@@ -54,6 +55,16 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
});
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
if (this.frm.doc.pos_profile) {
frappe.db
.get_value("POS Profile", this.frm.doc.pos_profile, "disable_grand_total_to_default_mop")
.then((r) => {
if (!r.exc) {
this.frm.skip_default_payment = r.message.disable_grand_total_to_default_mop;
}
});
}
}
onload_post_render(frm) {
@@ -112,6 +123,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
this.frm.meta.default_print_format = r.message.print_format || "";
this.frm.doc.campaign = r.message.campaign;
this.frm.allow_print_before_pay = r.message.allow_print_before_pay;
this.frm.skip_default_payment = r.message.skip_default_payment;
}
this.frm.script_manager.trigger("update_stock");
this.calculate_taxes_and_totals();

View File

@@ -62,6 +62,7 @@
"items_section",
"update_stock",
"scan_barcode",
"last_scanned_warehouse",
"items",
"pricing_rule_details",
"pricing_rules",
@@ -301,6 +302,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -1443,6 +1445,8 @@
"width": "50%"
},
{
"fetch_from": "sales_partner.commission_rate",
"fetch_if_empty": 1,
"fieldname": "commission_rate",
"fieldtype": "Float",
"label": "Commission Rate (%)",
@@ -1566,12 +1570,19 @@
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
},
{
"depends_on": "eval: doc.last_scanned_warehouse",
"fieldname": "last_scanned_warehouse",
"fieldtype": "Data",
"is_virtual": 1,
"label": "Last Scanned Warehouse"
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2024-11-26 13:10:50.309570",
"modified": "2025-08-04 22:22:31.471752",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@@ -216,6 +216,7 @@ class POSInvoice(SalesInvoice):
self.validate_loyalty_transaction()
self.validate_company_with_pos_company()
self.validate_full_payment()
self.update_packing_list()
if self.coupon_code:
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
@@ -367,9 +368,9 @@ class POSInvoice(SalesInvoice):
)
elif is_stock_item and flt(available_stock) < flt(d.stock_qty):
frappe.throw(
_(
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
).format(d.idx, item_code, warehouse, available_stock),
_("Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}.").format(
d.idx, item_code, warehouse
),
title=_("Item Unavailable"),
)
@@ -680,6 +681,7 @@ class POSInvoice(SalesInvoice):
"print_format": print_format,
"campaign": profile.get("campaign"),
"allow_print_before_pay": profile.get("allow_print_before_pay"),
"skip_default_payment": profile.get("disable_grand_total_to_default_mop"),
}
@frappe.whitelist()
@@ -774,10 +776,8 @@ def get_bundle_availability(bundle_item_code, warehouse):
bundle_bin_qty = 1000000
for item in product_bundle.items:
item_bin_qty = get_bin_qty(item.item_code, warehouse)
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
available_qty = item_bin_qty - item_pos_reserved_qty
max_available_bundles = available_qty / item.qty
max_available_bundles = item_bin_qty / item.qty
if bundle_bin_qty > max_available_bundles and frappe.get_value(
"Item", item.item_code, "is_stock_item"
):
@@ -800,13 +800,49 @@ def get_bin_qty(item_code, warehouse):
def get_pos_reserved_qty(item_code, warehouse):
"""
Calculate total quantity reserved for the given item and warehouse.
Includes:
- Direct sales of the item in submitted POS Invoices
- Sales of the item as a component of a Product Bundle
Excludes consolidated invoices (already merged into Sales Invoices via
POS Closing Entry). Used to reflect near real-time availability in the
POS UI and to prevent overselling while multiple sessions may be active.
"""
pinv_item_reserved_qty = get_pos_reserved_qty_from_table("POS Invoice Item", item_code, warehouse)
packed_item_reserved_qty = get_pos_reserved_qty_from_table("Packed Item", item_code, warehouse)
reserved_qty = pinv_item_reserved_qty + packed_item_reserved_qty
return reserved_qty
def get_pos_reserved_qty_from_table(child_table, item_code, warehouse):
"""
Get the total reserved quantity for a given item in POS Invoices
from a specific child table.
Args:
child_table (str): Name of the child table to query
(e.g., "POS Invoice Item", "Packed Item").
item_code (str): The Item Code to filter by.
warehouse (str): The Warehouse to filter by.
Returns:
float: The total reserved quantity for the item in the given
warehouse from submitted, unconsolidated POS Invoices.
"""
p_inv = frappe.qb.DocType("POS Invoice")
p_item = frappe.qb.DocType("POS Invoice Item")
p_item = frappe.qb.DocType(child_table)
qty_column = "qty" if child_table == "Packed Item" else "stock_qty"
reserved_qty = (
frappe.qb.from_(p_inv)
.from_(p_item)
.select(Sum(p_item.stock_qty).as_("stock_qty"))
.select(Sum(p_item[qty_column]).as_("stock_qty"))
.where(
(p_inv.name == p_item.parent)
& (IfNull(p_inv.consolidated_invoice, "") == "")

View File

@@ -1071,10 +1071,3 @@ def create_pos_invoice(**args):
pos_inv.payment_schedule = []
return pos_inv
def make_batch_item(item_name):
from erpnext.stock.doctype.item.test_item import make_item
if not frappe.db.exists(item_name):
return make_item(item_name, dict(has_batch_no=1, create_new_batch=1, is_stock_item=1))

View File

@@ -337,6 +337,11 @@ class POSInvoiceMergeLog(Document):
invoice.flags.ignore_pos_profile = True
invoice.pos_profile = ""
# Unset Commission Section
invoice.set("sales_partner", None)
invoice.set("commission_rate", 0)
invoice.set("total_commission", 0)
return invoice
def get_new_sales_invoice(self):

View File

@@ -135,6 +135,7 @@ frappe.ui.form.on("POS Profile", {
company: function (frm) {
frm.trigger("toggle_display_account_head");
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
erpnext.utils.set_letter_head(frm);
},
toggle_display_account_head: function (frm) {

View File

@@ -92,6 +92,7 @@ frappe.ui.form.on("Process Statement Of Accounts", {
frm.set_value("account", "");
frm.set_value("cost_center", "");
frm.set_value("project", "");
erpnext.utils.set_letter_head(frm);
},
report: function (frm) {
let filters = {

View File

@@ -78,18 +78,18 @@
"reqd": 1
},
{
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
"depends_on": "eval:(!doc.enable_auto_email && doc.report == 'General Ledger');",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"mandatory_depends_on": "eval:doc.frequency == '';"
"mandatory_depends_on": "eval:(!doc.enable_auto_email && doc.report == \"General Ledger\") "
},
{
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
"depends_on": "eval:(!doc.enable_auto_email && doc.report == 'General Ledger');",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"mandatory_depends_on": "eval:doc.frequency == '';"
"mandatory_depends_on": "eval:(!doc.enable_auto_email && doc.report == \"General Ledger\") "
},
{
"fieldname": "cost_center",
@@ -330,7 +330,8 @@
"depends_on": "eval:(doc.report == 'Accounts Receivable');",
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date"
"label": "Posting Date",
"mandatory_depends_on": "eval:(doc.report == 'Accounts Receivable');"
},
{
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
@@ -400,7 +401,7 @@
}
],
"links": [],
"modified": "2025-07-08 16:52:12.602384",
"modified": "2025-08-04 18:21:12.603623",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",

View File

@@ -550,7 +550,7 @@ def send_auto_email():
selected = frappe.get_list(
"Process Statement Of Accounts",
filters={"enable_auto_email": 1},
or_filters={"to_date": format_date(today()), "posting_date": format_date(today())},
or_filters={"to_date": today(), "posting_date": today()},
)
for entry in selected:
send_emails(entry.name, from_scheduler=True)

View File

@@ -3,7 +3,7 @@
import frappe
from frappe.model.document import Document
from frappe.utils import getdate
from frappe.utils import create_batch, getdate
from erpnext.accounts.doctype.subscription.subscription import DateTimeLikeObject, process_all
@@ -23,7 +23,23 @@ class ProcessSubscription(Document):
# end: auto-generated types
def on_submit(self):
process_all(subscription=self.subscription, posting_date=self.posting_date)
self.process_all_subscription()
def process_all_subscription(self):
filters = {"status": ("!=", "Cancelled")}
if self.subscription:
filters["name"] = self.subscription
subscriptions = frappe.get_all("Subscription", filters, pluck="name")
for subscription in create_batch(subscriptions, 500):
frappe.enqueue(
method="erpnext.accounts.doctype.subscription.subscription.process_all",
queue="long",
subscription=subscription,
posting_date=self.posting_date,
)
def create_subscription_process(

View File

@@ -35,7 +35,10 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
this.frm.set_query("expense_account", "items", function () {
return {
query: "erpnext.controllers.queries.get_expense_account",
filters: { company: doc.company },
filters: {
company: doc.company,
disabled: 0,
},
};
});
}
@@ -150,6 +153,9 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
per_billed: ["<", 99.99],
company: me.frm.doc.company,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
});
},
__("Get Items From")
@@ -172,6 +178,9 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
company: me.frm.doc.company,
is_return: 0,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
});
},
__("Get Items From")

View File

@@ -47,6 +47,7 @@
"ignore_pricing_rule",
"sec_warehouse",
"scan_barcode",
"last_scanned_warehouse",
"col_break_warehouse",
"update_stock",
"set_warehouse",
@@ -319,6 +320,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -1643,6 +1645,13 @@
"label": "Select Dispatch Address ",
"options": "Address",
"print_hide": 1
},
{
"depends_on": "eval: doc.last_scanned_warehouse",
"fieldname": "last_scanned_warehouse",
"fieldtype": "Data",
"is_virtual": 1,
"label": "Last Scanned Warehouse"
}
],
"grid_page_length": 50,
@@ -1650,7 +1659,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2025-04-09 16:49:22.175081",
"modified": "2025-08-04 19:19:11.380664",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe import _, qb, throw
from frappe.model.mapper import get_mapped_doc
@@ -970,7 +972,7 @@ class PurchaseInvoice(BuyingController):
self.get_provisional_accounts()
for item in self.get("items"):
if flt(item.base_net_amount):
if flt(item.base_net_amount) or (self.get("update_stock") and item.valuation_rate):
if item.item_code:
frappe.get_cached_value("Item", item.item_code, "asset_category")
@@ -1223,6 +1225,9 @@ class PurchaseInvoice(BuyingController):
def get_provisional_accounts(self):
self.provisional_accounts = frappe._dict()
linked_purchase_receipts = set([d.purchase_receipt for d in self.items if d.purchase_receipt])
if not linked_purchase_receipts:
return
pr_items = frappe.get_all(
"Purchase Receipt Item",
filters={"parent": ("in", linked_purchase_receipts)},
@@ -1307,8 +1312,37 @@ class PurchaseInvoice(BuyingController):
net_amt_precision,
)
# Stock ledger value is not matching with the warehouse amount
if (
if self.is_return and self.update_stock and (self.is_internal_supplier or not self.return_against):
net_rate = item.base_net_amount
if item.sales_incoming_rate: # for internal transfer
net_rate = item.qty * item.sales_incoming_rate
stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
warehouse_debit_amount = flt(
voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision
)
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_adjustment_amt = stock_amount - warehouse_debit_amount
gl_entries.append(
self.get_gl_dict(
{
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
elif (
self.update_stock
and voucher_wise_stock_value.get((item.name, item.warehouse))
and warehouse_debit_amount
@@ -1336,33 +1370,6 @@ class PurchaseInvoice(BuyingController):
warehouse_debit_amount = stock_amount
elif self.is_return and self.update_stock and (self.is_internal_supplier or not self.return_against):
net_rate = item.base_net_amount
if item.sales_incoming_rate: # for internal transfer
net_rate = item.qty * item.sales_incoming_rate
stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_adjustment_amt = stock_amount - warehouse_debit_amount
gl_entries.append(
self.get_gl_dict(
{
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
return warehouse_debit_amount
def make_tax_gl_entries(self, gl_entries):
@@ -2065,7 +2072,12 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
@frappe.whitelist()
def make_purchase_receipt(source_name, target_doc=None):
def make_purchase_receipt(source_name, target_doc=None, args=None):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
def update_item(obj, target, source_parent):
target.qty = flt(obj.qty) - flt(obj.received_qty)
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
@@ -2075,6 +2087,11 @@ def make_purchase_receipt(source_name, target_doc=None):
(flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) * flt(source_parent.conversion_rate)
)
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
doc = get_mapped_doc(
"Purchase Invoice",
source_name,
@@ -2098,7 +2115,7 @@ def make_purchase_receipt(source_name, target_doc=None):
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and select_item(doc),
},
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges"},
},

View File

@@ -5,6 +5,7 @@ import inspect
import frappe
from frappe import _, qb
from frappe.desk.form.linked_with import get_child_tables_of_doctypes
from frappe.model.document import Document
from frappe.utils.data import comma_and
@@ -169,11 +170,15 @@ def start_repost(account_repost_doc=str) -> None:
frappe.db.delete(
"Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}
)
frappe.db.delete(
"Advance Payment Ledger Entry",
filters={"voucher_type": doc.doctype, "voucher_no": doc.name},
)
if doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
if not repost_doc.delete_cancelled_entries:
doc.docstatus = 2
doc.make_gl_entries_on_cancel()
doc.make_gl_entries_on_cancel(from_repost=True)
doc.docstatus = 1
if doc.doctype == "Sales Invoice":
@@ -185,7 +190,7 @@ def start_repost(account_repost_doc=str) -> None:
elif doc.doctype == "Purchase Receipt":
if not repost_doc.delete_cancelled_entries:
doc.docstatus = 2
doc.make_gl_entries_on_cancel()
doc.make_gl_entries_on_cancel(from_repost=True)
doc.docstatus = 1
doc.make_gl_entries(from_repost=True)
@@ -204,13 +209,29 @@ def start_repost(account_repost_doc=str) -> None:
doc.make_gl_entries()
def get_allowed_types_from_settings():
return [
def get_allowed_types_from_settings(child_doc: bool = False):
repost_docs = [
x.document_type
for x in frappe.db.get_all(
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
)
]
result = repost_docs
if repost_docs and child_doc:
result.extend(get_child_docs(repost_docs))
return result
def get_child_docs(doc: list) -> list:
child_doc = []
doc = get_child_tables_of_doctypes(doc)
for child_list in doc.values():
for child in child_list:
if child.get("child_table"):
child_doc.append(child["child_table"])
return child_doc
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):

View File

@@ -1,9 +1,14 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
import frappe
from frappe.model.document import Document
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import get_child_docs
class RepostAccountingLedgerSettings(Document):
# begin: auto-generated types
@@ -17,6 +22,24 @@ class RepostAccountingLedgerSettings(Document):
from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes
allowed_types: DF.Table[RepostAllowedTypes]
# end: auto-generated types
pass
# end: auto-generated types
def validate(self):
self.update_property_for_accounting_dimension()
def update_property_for_accounting_dimension(self):
doctypes = [entry.document_type for entry in self.allowed_types if entry.allowed]
if not doctypes:
return
doctypes += get_child_docs(doctypes)
set_allow_on_submit_for_dimension_fields(doctypes)
def set_allow_on_submit_for_dimension_fields(doctypes):
for dt in doctypes:
meta = frappe.get_meta(dt)
for dimension in get_accounting_dimensions():
df = meta.get_field(dimension)
if df and not df.allow_on_submit:
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)

View File

@@ -8,7 +8,7 @@ from frappe import _, qb
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from erpnext.accounts.utils import _delete_pl_entries, create_payment_ledger_entry
from erpnext.accounts.utils import _delete_adv_pl_entries, _delete_pl_entries, create_payment_ledger_entry
VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
@@ -16,6 +16,7 @@ VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal
def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None):
if voucher_type and voucher_no and gle_map:
_delete_pl_entries(voucher_type, voucher_no)
_delete_adv_pl_entries(voucher_type, voucher_no)
create_payment_ledger_entry(gle_map, cancel=0)

View File

@@ -58,6 +58,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
me.frm.script_manager.trigger("is_pos");
me.frm.refresh_fields();
frappe.db
.get_value("POS Profile", this.frm.doc.pos_profile, "disable_grand_total_to_default_mop")
.then((r) => {
if (!r.exc) {
me.frm.skip_default_payment = r.message.disable_grand_total_to_default_mop;
}
});
}
erpnext.queries.setup_warehouse_query(this.frm);
}
@@ -259,6 +266,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
per_billed: ["<", 99.99],
company: me.frm.doc.company,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
});
},
__("Get Items From")
@@ -288,6 +298,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
status: ["!=", "Lost"],
company: me.frm.doc.company,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "rate", "amount"],
});
},
__("Get Items From")
@@ -319,6 +332,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
filters: filters,
};
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
});
},
__("Get Items From")
@@ -497,8 +513,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
},
callback: function (r) {
if (!r.exc) {
if (r.message && r.message.print_format) {
if (r.message) {
me.frm.pos_print_format = r.message.print_format;
me.frm.skip_default_payment = r.message.skip_default_payment;
}
me.frm.trigger("update_stock");
if (me.frm.doc.taxes_and_charges) {

View File

@@ -45,6 +45,7 @@
"items_section",
"scan_barcode",
"update_stock",
"last_scanned_warehouse",
"column_break_39",
"set_warehouse",
"set_target_warehouse",
@@ -379,6 +380,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"hide_days": 1,
@@ -2176,6 +2178,13 @@
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
},
{
"depends_on": "eval: doc.last_scanned_warehouse",
"fieldname": "last_scanned_warehouse",
"fieldtype": "Data",
"is_virtual": 1,
"label": "Last Scanned Warehouse"
}
],
"grid_page_length": 50,
@@ -2189,7 +2198,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2025-06-26 14:06:56.773552",
"modified": "2025-08-04 19:20:28.732039",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -461,6 +461,7 @@ class SalesInvoice(SellingController):
self.make_bundle_for_sales_purchase_return(table_name)
self.make_bundle_using_old_serial_batch_fields(table_name)
self.validate_standalone_serial_nos_customer()
self.update_stock_reservation_entries()
self.update_stock_ledger()
@@ -699,6 +700,7 @@ class SalesInvoice(SellingController):
"allow_edit_discount": pos.get("allow_user_to_edit_discount"),
"campaign": pos.get("campaign"),
"allow_print_before_pay": pos.get("allow_print_before_pay"),
"skip_default_payment": pos.get("disable_grand_total_to_default_mop"),
}
def update_time_sheet(self, sales_invoice):
@@ -1205,7 +1207,6 @@ class SalesInvoice(SellingController):
self.make_exchange_gain_loss_journal()
elif self.docstatus == 2:
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No":
@@ -1369,8 +1370,9 @@ class SalesInvoice(SellingController):
)
asset.db_set("disposal_date", None)
add_asset_activity(asset.name, _("Asset returned"))
asset_status = asset.get_status()
if asset.calculate_depreciation:
if asset.calculate_depreciation and not asset_status == "Fully Depreciated":
posting_date = (
frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
if self.is_return

View File

@@ -8,7 +8,7 @@ import frappe
from frappe import qb
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, format_date, getdate, nowdate, today
from frappe.utils import add_days, cint, flt, format_date, getdate, nowdate, today
import erpnext
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
@@ -3055,6 +3055,28 @@ class TestSalesInvoice(FrappeTestCase):
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
# cases where distributed discount amount is not set
frappe.db.set_value(
"Sales Invoice Item",
{"name": ["in", [d.name for d in si.items]]},
"distributed_discount_amount",
0,
)
si.load_from_db()
si.additional_discount_account = additional_discount_account
# Ledger reposted implicitly upon 'Update After Submit'
si.save()
expected_gle = [
["Debtors - _TC", 88, 0.0, nowdate()],
["Discount Account - _TC", 22.0, 0.0, nowdate()],
["Service - _TC", 0.0, 100.0, nowdate()],
["TDS Payable - _TC", 0.0, 10.0, nowdate()],
]
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
def test_asset_depreciation_on_sale_with_pro_rata(self):
"""
Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale.
@@ -4553,6 +4575,152 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(stock_ledger_entry.qty, 2.0)
self.assertEqual(stock_ledger_entry.stock_value_difference, 0.0)
def test_non_batchwise_valuation_for_moving_average(self):
from erpnext.stock.doctype.item.test_item import make_item
item_code = "_Test Item for Non Batchwise Valuation"
make_item_for_si(
item_code,
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "TBATCH-TCNV.####",
"valuation_method": "Moving Average",
},
)
doc = frappe.get_doc("Stock Settings")
original_value = cint(doc.do_not_use_batchwise_valuation)
doc.db_set("do_not_use_batchwise_valuation", 1)
se = make_stock_entry(
item_code=item_code,
qty=10,
target="_Test Warehouse - _TC",
rate=13.02,
valuation_method="Moving Average",
use_serial_batch_fields=True,
)
se_batch = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
# without use serial and batch fields
si = create_sales_invoice(
item=item_code,
qty=1,
rate=120,
update_stock=1,
use_serial_batch_fields=False,
warehouse="_Test Warehouse - _TC",
)
si.reload()
si_batch = get_batch_from_bundle(si.items[0].serial_and_batch_bundle)
self.assertEqual(se_batch, si_batch)
self.assertEqual(si.items[0].use_serial_batch_fields, 0)
serial_and_batch_bundle = si.items[0].serial_and_batch_bundle
change_in_value = frappe.db.get_value(
"Stock Ledger Entry",
{
"voucher_type": "Sales Invoice",
"voucher_no": si.name,
"item_code": item_code,
"warehouse": "_Test Warehouse - _TC",
"serial_and_batch_bundle": serial_and_batch_bundle,
},
"stock_value_difference",
)
self.assertEqual(change_in_value, 13.02 * -1)
# with use serial and batch fields
si = create_sales_invoice(
item=item_code,
qty=1,
rate=120,
update_stock=1,
use_serial_batch_fields=True,
warehouse="_Test Warehouse - _TC",
)
si.reload()
self.assertEqual(si.items[0].use_serial_batch_fields, 1)
serial_and_batch_bundle = si.items[0].serial_and_batch_bundle
change_in_value = frappe.db.get_value(
"Stock Ledger Entry",
{
"voucher_type": "Sales Invoice",
"voucher_no": si.name,
"item_code": item_code,
"warehouse": "_Test Warehouse - _TC",
"serial_and_batch_bundle": serial_and_batch_bundle,
},
"stock_value_difference",
)
self.assertEqual(change_in_value, 13.02 * -1)
doc.db_set("do_not_use_batchwise_valuation", original_value)
def test_system_generated_exchange_gain_or_loss_je_after_repost(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
update_repost_settings,
)
update_repost_settings()
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=80,
)
pe = get_payment_entry("Sales Invoice", si.name)
pe.reference_no = "10"
pe.reference_date = nowdate()
pe.paid_from_account_currency = si.currency
pe.paid_to_account_currency = "INR"
pe.source_exchange_rate = 85
pe.target_exchange_rate = 1
pe.paid_amount = si.outstanding_amount
pe.insert()
pe.submit()
ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = si.company
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
ral.save()
ral.submit()
je = frappe.qb.DocType("Journal Entry")
jea = frappe.qb.DocType("Journal Entry Account")
q = (
(
frappe.qb.from_(je)
.join(jea)
.on(je.name == jea.parent)
.select(je.docstatus)
.where(
(je.voucher_type == "Exchange Gain Or Loss")
& (jea.reference_name == si.name)
& (jea.reference_type == "Sales Invoice")
& (je.is_system_generated == 1)
)
)
.limit(1)
.run()
)
self.assertEqual(q[0][0], 1)
def make_item_for_si(item_code, properties=None):
from erpnext.stock.doctype.item.test_item import make_item
@@ -4668,6 +4836,7 @@ def create_sales_invoice(**args):
"incoming_rate": args.incoming_rate or 0,
"serial_and_batch_bundle": bundle_id,
"allow_zero_valuation_rate": args.allow_zero_valuation_rate or 0,
"use_serial_batch_fields": args.use_serial_batch_fields or 0,
},
)

View File

@@ -756,18 +756,14 @@ def get_prorata_factor(
return diff / plan_days
def process_all(subscription: str | None = None, posting_date: DateTimeLikeObject | None = None) -> None:
def process_all(subscription: list, posting_date: DateTimeLikeObject | None = None) -> None:
"""
Task to updates the status of all `Subscription` apart from those that are cancelled
"""
filters = {"status": ("!=", "Cancelled")}
if subscription:
filters["name"] = subscription
for subscription in frappe.get_all("Subscription", filters, pluck="name"):
for subscription_name in subscription:
try:
subscription = frappe.get_doc("Subscription", subscription)
subscription = frappe.get_doc("Subscription", subscription_name)
subscription.process(posting_date)
frappe.db.commit()
except frappe.ValidationError:

View File

@@ -75,7 +75,7 @@
},
{
"default": "0",
"description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
"description": "Only payment entries with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
"fieldname": "consider_party_ledger_amount",
"fieldtype": "Check",
"label": "Consider Entire Party Ledger Amount",
@@ -102,10 +102,11 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-07-27 21:47:34.396071",
"modified": "2025-07-30 07:13:51.785735",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withholding Category",
"naming_rule": "Set by user",
"owner": "Administrator",
"permissions": [
{
@@ -148,4 +149,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -9,6 +9,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
@@ -17,6 +18,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
def setUp(self):
self.create_company()
self.create_customer()
self.create_supplier()
self.create_usd_receivable_account()
self.create_item()
self.clear_old_entries()
@@ -364,13 +366,13 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
# Assert 'Advance Paid'
so.reload()
pe.reload()
self.assertEqual(so.advance_paid, 100)
self.assertEqual(so.advance_paid, 0)
self.assertEqual(len(pe.references), 0)
self.assertEqual(pe.unallocated_amount, 100)
pe.cancel()
so.reload()
self.assertEqual(so.advance_paid, 100)
self.assertEqual(so.advance_paid, 0)
def test_06_unreconcile_advance_from_payment_entry(self):
self.enable_advance_as_liability()
@@ -417,7 +419,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
so2.reload()
pe.reload()
self.assertEqual(so1.advance_paid, 150)
self.assertEqual(so2.advance_paid, 110)
self.assertEqual(so2.advance_paid, 0)
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.unallocated_amount, 110)
@@ -463,8 +465,77 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
# Assert 'Advance Paid'
so.reload()
self.assertEqual(so.advance_paid, 1000)
unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payment",
"company": self.company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
}
)
unreconcile.add_references()
unreconcile.allocations = [x for x in unreconcile.allocations if x.reference_name == si.name]
unreconcile.save().submit()
# after unreconcilaition advance paid will be reduced
# Assert 'Advance Paid'
so.reload()
self.assertEqual(so.advance_paid, 0)
self.disable_advance_as_liability()
def test_unreconcile_advance_from_journal_entry(self):
po = create_purchase_order(
company=self.company,
supplier=self.supplier,
item=self.item,
qty=1,
rate=100,
transaction_date=today(),
do_not_submit=False,
)
je = frappe.get_doc(
{
"doctype": "Journal Entry",
"company": self.company,
"voucher_type": "Journal Entry",
"posting_date": po.transaction_date,
"multi_currency": True,
"accounts": [
{
"account": "Creditors - _TC",
"party_type": "Supplier",
"party": po.supplier,
"debit_in_account_currency": 100,
"is_advance": "Yes",
"reference_type": po.doctype,
"reference_name": po.name,
},
{"account": "Cash - _TC", "credit_in_account_currency": 100},
],
}
)
je.save().submit()
po.reload()
self.assertEqual(po.advance_paid, 100)
unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payment",
"company": self.company,
"voucher_type": je.doctype,
"voucher_no": je.name,
}
)
unreconcile.add_references()
self.assertEqual(len(unreconcile.allocations), 1)
allocations = [x.reference_name for x in unreconcile.allocations]
self.assertEqual([po.name], allocations)
unreconcile.save().submit()
po.reload()
self.assertEqual(po.advance_paid, 0)

View File

@@ -12,7 +12,6 @@ from frappe.utils.data import comma_and
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_advance_payment_doctypes,
unlink_ref_doc_from_payment_entries,
update_voucher_outstanding,
)
@@ -45,31 +44,12 @@ class UnreconcilePayment(Document):
@frappe.whitelist()
def get_allocations_from_payment(self):
allocated_references = []
ple = qb.DocType("Payment Ledger Entry")
allocated_references = (
qb.from_(ple)
.select(
ple.account,
ple.party_type,
ple.party,
ple.against_voucher_type.as_("reference_doctype"),
ple.against_voucher_no.as_("reference_name"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
.where(
(ple.docstatus == 1)
& (ple.voucher_type == self.voucher_type)
& (ple.voucher_no == self.voucher_no)
& (ple.voucher_no != ple.against_voucher_no)
)
.groupby(ple.against_voucher_type, ple.against_voucher_no)
.run(as_dict=True)
return get_linked_payments_for_doc(
company=self.company,
doctype=self.voucher_type,
docname=self.voucher_no,
)
return allocated_references
def add_references(self):
allocations = self.get_allocations_from_payment()
@@ -82,27 +62,43 @@ class UnreconcilePayment(Document):
doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name)
unlink_ref_doc_from_payment_entries(doc, self.voucher_no)
cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no)
# update outstanding amounts
update_voucher_outstanding(
alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party
alloc.reference_doctype,
alloc.reference_name,
alloc.account,
alloc.party_type,
alloc.party,
)
if doc.doctype in get_advance_payment_doctypes():
doc.set_total_advance_paid()
frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
@frappe.whitelist()
def doc_has_references(doctype: str | None = None, docname: str | None = None):
count = 0
if doctype in ["Sales Invoice", "Purchase Invoice"]:
return frappe.db.count(
count = frappe.db.count(
"Payment Ledger Entry",
filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]},
)
else:
return frappe.db.count(
count = frappe.db.count(
"Payment Ledger Entry",
filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]},
)
count += frappe.db.count(
"Advance Payment Ledger Entry",
filters={
"delinked": 0,
"voucher_no": docname,
"voucher_type": doctype,
"event": ["=", "Submit"],
},
)
return count
@frappe.whitelist()
@@ -124,9 +120,12 @@ def get_linked_payments_for_doc(
res = (
qb.from_(ple)
.select(
ple.account,
ple.party_type,
ple.party,
ple.company,
ple.voucher_type,
ple.voucher_no,
ple.voucher_type.as_("reference_doctype"),
ple.voucher_no.as_("reference_name"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
@@ -148,19 +147,52 @@ def get_linked_payments_for_doc(
qb.from_(ple)
.select(
ple.company,
ple.against_voucher_type.as_("voucher_type"),
ple.against_voucher_no.as_("voucher_no"),
ple.account,
ple.party_type,
ple.party,
ple.against_voucher_type.as_("reference_doctype"),
ple.against_voucher_no.as_("reference_name"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
.where(Criterion.all(criteria))
.groupby(ple.against_voucher_no)
)
res = query.run(as_dict=True)
res += get_linked_advances(company, _dn)
return res
return []
def get_linked_advances(company, docname):
adv = qb.DocType("Advance Payment Ledger Entry")
criteria = [
(adv.company == company),
(adv.delinked == 0),
(adv.voucher_no == docname),
(adv.event == "Submit"),
]
return (
qb.from_(adv)
.select(
adv.company,
adv.against_voucher_type.as_("reference_doctype"),
adv.against_voucher_no.as_("reference_name"),
Abs(Sum(adv.amount)).as_("allocated_amount"),
adv.currency,
)
.where(Criterion.all(criteria))
.having(qb.Field("allocated_amount") > 0)
.groupby(adv.against_voucher_no)
.run(as_dict=True)
)
@frappe.whitelist()
def create_unreconcile_doc_for_selection(selections=None):
if selections:

View File

@@ -186,6 +186,15 @@ def process_gl_map(gl_map, merge_entries=True, precision=None, from_repost=False
def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_repost=False):
round_off_account, default_currency = frappe.get_cached_value(
"Company", gl_map[0].company, ["round_off_account", "default_currency"]
)
if not precision:
precision = get_field_precision(
frappe.get_meta("GL Entry").get_field("debit"),
currency=default_currency,
)
new_gl_map = []
for d in gl_map:
cost_center = d.get("cost_center")
@@ -203,6 +212,11 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_r
new_gl_map.append(d)
continue
if d.account == round_off_account:
d.cost_center = cost_center_allocation[0][0]
new_gl_map.append(d)
continue
for sub_cost_center, percentage in cost_center_allocation:
gle = copy.deepcopy(d)
gle.cost_center = sub_cost_center
@@ -305,6 +319,8 @@ def get_merge_properties(dimensions=None):
"project",
"finance_book",
"voucher_no",
"advance_voucher_type",
"advance_voucher_no",
]
if dimensions:
merge_properties.extend(dimensions)

View File

@@ -23,6 +23,13 @@ frappe.query_reports["Accounts Payable Summary"] = {
options: "Posting Date\nDue Date",
default: "Due Date",
},
{
fieldname: "calculate_ageing_with",
label: __("Calculate Ageing With"),
fieldtype: "Select",
options: "Report Date\nToday Date",
default: "Report Date",
},
{
fieldname: "range",
label: __("Ageing Range"),

View File

@@ -6,7 +6,7 @@ from collections import OrderedDict
import frappe
from frappe import _, qb, query_builder, scrub
from frappe.desk.reportview import build_match_conditions
from frappe.database.schema import get_definition
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Date, Substring, Sum
from frappe.utils import cint, cstr, flt, getdate, nowdate
@@ -16,6 +16,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_dimension_with_children,
)
from erpnext.accounts.utils import (
build_qb_match_conditions,
get_advance_payment_doctypes,
get_currency_precision,
get_party_types_from_account_type,
@@ -127,6 +128,8 @@ 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()
@@ -134,8 +137,7 @@ class ReceivablePayableReport:
self.build_data()
def fetch_ple_in_buffered_cursor(self):
query, param = self.ple_query
self.ple_entries = frappe.db.sql(query, param, as_dict=True)
self.ple_entries = self.ple_query.run(as_dict=True)
for ple in self.ple_entries:
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
@@ -148,9 +150,8 @@ class ReceivablePayableReport:
def fetch_ple_in_unbuffered_cursor(self):
self.ple_entries = []
query, param = self.ple_query
with frappe.db.unbuffered_cursor():
for ple in frappe.db.sql(query, param, as_dict=True, as_iterator=True):
for ple in self.ple_query.run(as_dict=True, as_iterator=True):
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
self.ple_entries.append(ple)
@@ -318,6 +319,79 @@ 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,
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",
]:
_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)
@@ -861,18 +935,15 @@ class ReceivablePayableReport:
else:
query = query.select(ple.remarks)
query, param = query.walk()
match_conditions = build_match_conditions("Payment Ledger Entry")
if match_conditions:
query += " AND " + match_conditions
if match_conditions := build_qb_match_conditions("Payment Ledger Entry"):
query = query.where(Criterion.all(match_conditions))
if self.filters.get("group_by_party"):
query += f" ORDER BY `{self.ple.party.name}`, `{self.ple.posting_date.name}`"
query = query.orderby(self.ple.party, self.ple.posting_date)
else:
query += f" ORDER BY `{self.ple.posting_date.name}`, `{self.ple.party.name}`"
query = query.orderby(self.ple.posting_date, self.ple.party)
self.ple_query = (query, param)
self.ple_query = query
def get_sales_invoices_or_customers_based_on_sales_person(self):
if self.filters.get("sales_person"):
@@ -1253,3 +1324,134 @@ def get_customer_group_with_children(customer_groups):
frappe.throw(_("Customer Group: {0} does not exist").format(d))
return list(set(all_customer_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},
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},
party {_varchar_type},
posting_date date,
due_date date,
account_currency {_varchar_type},
amount {_currency_type},
amount_in_account_currency {_currency_type}) engine=memory;
"""
# Function
genkey_function_name = "ar_genkey"
genkey_function_sql = f"""
create function `{genkey_function_name}`(rec row type of `{_row_def_table_name}`, allocate bool) returns char(40)
begin
if allocate then
return sha1(concat_ws(',', rec.account, rec.against_voucher_type, rec.against_voucher_no, rec.party));
else
return sha1(concat_ws(',', rec.account, rec.voucher_type, rec.voucher_no, rec.party));
end if;
end
"""
# 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 = `{genkey_function_name}`(ple, false))
then
insert into `{_voucher_balance_name}` values (`{genkey_function_name}`(ple, false), ple.voucher_type, ple.voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency, ple.cost_center, 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 (`{genkey_function_name}`(ple, true), 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.genkey_function_name not in existing_procedures:
frappe.db.sql(self.genkey_function_sql)
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

@@ -23,6 +23,13 @@ frappe.query_reports["Accounts Receivable Summary"] = {
options: "Posting Date\nDue Date",
default: "Due Date",
},
{
fieldname: "calculate_ageing_with",
label: __("Calculate Ageing With"),
fieldtype: "Select",
options: "Report Date\nToday Date",
default: "Report Date",
},
{
fieldname: "range",
label: __("Ageing Range"),

View File

@@ -25,17 +25,25 @@ def get_group_by_asset_category_data(filters):
asset_categories = get_asset_categories_for_grouped_by_category(filters)
assets = get_assets_for_grouped_by_category(filters)
asset_value_adjustment_map = get_asset_value_adjustment_map_by_category(filters)
for asset_category in asset_categories:
row = frappe._dict()
row.update(asset_category)
adjustments = asset_value_adjustment_map.get(asset_category.get("asset_category"), {})
row.adjustment_before_from_date = flt(adjustments.get("adjustment_before_from_date", 0))
row.adjustment_till_to_date = flt(adjustments.get("adjustment_till_to_date", 0))
row.adjustment_during_period = row.adjustment_till_to_date - row.adjustment_before_from_date
row.value_as_on_from_date += row.adjustment_before_from_date
row.value_as_on_to_date = (
flt(row.value_as_on_from_date)
+ flt(row.value_of_new_purchase)
- flt(row.value_of_sold_asset)
- flt(row.value_of_scrapped_asset)
- flt(row.value_of_capitalized_asset)
+ flt(row.adjustment_during_period)
)
row.update(
@@ -229,26 +237,93 @@ def get_assets_for_grouped_by_category(filters):
)
def get_asset_value_adjustment_map_by_category(filters):
asset_value_adjustments = frappe.db.sql(
"""
SELECT
a.asset_category AS asset_category,
IFNULL(
SUM(
CASE
WHEN gle.posting_date < %(from_date)s
AND (a.disposal_date IS NULL OR a.disposal_date >= %(from_date)s)
THEN gle.debit - gle.credit
ELSE 0
END
),
0) AS value_adjustment_before_from_date,
IFNULL(
SUM(
CASE
WHEN gle.posting_date <= %(to_date)s
AND (a.disposal_date IS NULL OR a.disposal_date >= %(to_date)s)
THEN gle.debit - gle.credit
ELSE 0
END
),
0) AS value_adjustment_till_to_date
FROM `tabGL Entry` gle
JOIN `tabAsset` a ON gle.against_voucher = a.name
JOIN `tabAsset Category Account` aca
ON aca.parent = a.asset_category
AND aca.company_name = %(company)s
WHERE gle.is_cancelled = 0
AND a.docstatus = 1
AND a.company = %(company)s
AND a.purchase_date <= %(to_date)s
AND gle.account = aca.fixed_asset_account
GROUP BY a.asset_category
""",
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
as_dict=1,
)
category_value_adjustment_map = {}
for r in asset_value_adjustments:
category_value_adjustment_map[r["asset_category"]] = {
"adjustment_before_from_date": flt(r.get("value_adjustment_before_from_date", 0)),
"adjustment_till_to_date": flt(r.get("value_adjustment_till_to_date", 0)),
}
return category_value_adjustment_map
def get_group_by_asset_data(filters):
data = []
asset_details = get_asset_details_for_grouped_by_category(filters)
assets = get_assets_for_grouped_by_asset(filters)
asset_value_adjustment_map = get_asset_value_adjustment_map(filters)
for asset_detail in asset_details:
row = frappe._dict()
row.update(asset_detail)
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
adjustments = asset_value_adjustment_map.get(
asset_detail.get("name", ""),
{
"adjustment_before_from_date": 0.0,
"adjustment_till_to_date": 0.0,
},
)
row.adjustment_before_from_date = adjustments["adjustment_before_from_date"]
row.adjustment_till_to_date = adjustments["adjustment_till_to_date"]
row.adjustment_during_period = flt(row.adjustment_till_to_date) - flt(row.adjustment_before_from_date)
row.value_as_on_from_date += row.adjustment_before_from_date
row.value_as_on_to_date = (
flt(row.value_as_on_from_date)
+ flt(row.value_of_new_purchase)
- flt(row.value_of_sold_asset)
- flt(row.value_of_scrapped_asset)
- flt(row.value_of_capitalized_asset)
+ flt(row.adjustment_during_period)
)
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
row.accumulated_depreciation_as_on_to_date = (
flt(row.accumulated_depreciation_as_on_from_date)
+ flt(row.depreciation_amount_during_the_period)
@@ -432,6 +507,59 @@ def get_assets_for_grouped_by_asset(filters):
)
def get_asset_value_adjustment_map(filters):
asset_with_value_adjustments = frappe.db.sql(
"""
SELECT
a.name AS asset,
IFNULL(
SUM(
CASE
WHEN gle.posting_date < %(from_date)s
AND (a.disposal_date IS NULL OR a.disposal_date >= %(from_date)s)
THEN gle.debit - gle.credit
ELSE 0
END
),
0) AS value_adjustment_before_from_date,
IFNULL(
SUM(
CASE
WHEN gle.posting_date <= %(to_date)s
AND (a.disposal_date IS NULL OR a.disposal_date >= %(to_date)s)
THEN gle.debit - gle.credit
ELSE 0
END
),
0) AS value_adjustment_till_to_date
FROM `tabGL Entry` gle
JOIN `tabAsset` a ON gle.against_voucher = a.name
JOIN `tabAsset Category Account` aca
ON aca.parent = a.asset_category
AND aca.company_name = %(company)s
WHERE gle.is_cancelled = 0
AND a.docstatus = 1
AND a.company = %(company)s
AND a.purchase_date <= %(to_date)s
AND gle.account = aca.fixed_asset_account
GROUP BY a.name
""",
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
as_dict=1,
)
asset_value_adjustment_map = {}
for r in asset_with_value_adjustments:
asset_value_adjustment_map[r["asset"]] = {
"adjustment_before_from_date": flt(r.get("value_adjustment_before_from_date", 0)),
"adjustment_till_to_date": flt(r.get("value_adjustment_till_to_date", 0)),
}
return asset_value_adjustment_map
def get_columns(filters):
columns = []

View File

@@ -14,9 +14,16 @@ erpnext.utils.add_dimensions("Cash Flow", 10);
frappe.query_reports["Cash Flow"]["filters"].splice(8, 1);
frappe.query_reports["Cash Flow"]["filters"].push({
fieldname: "include_default_book_entries",
label: __("Include Default FB Entries"),
fieldtype: "Check",
default: 1,
});
frappe.query_reports["Cash Flow"]["filters"].push(
{
fieldname: "include_default_book_entries",
label: __("Include Default FB Entries"),
fieldtype: "Check",
default: 1,
},
{
fieldname: "show_opening_and_closing_balance",
label: __("Show Opening and Closing Balance"),
fieldtype: "Check",
}
);

View File

@@ -2,9 +2,13 @@
# For license information, please see license.txt
from datetime import timedelta
import frappe
from frappe import _
from frappe.utils import cstr
from frappe.query_builder import DocType
from frappe.utils import cstr, flt
from pypika import Order
from erpnext.accounts.report.financial_statements import (
get_columns,
@@ -12,6 +16,7 @@ from erpnext.accounts.report.financial_statements import (
get_data,
get_filtered_list_for_consolidated_report,
get_period_list,
set_gl_entries_by_account,
)
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
get_net_profit_loss,
@@ -119,10 +124,20 @@ def execute(filters=None):
filters,
)
add_total_row_account(
net_change_in_cash = add_total_row_account(
data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters
)
columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company, True)
if filters.show_opening_and_closing_balance:
show_opening_and_closing_balance(data, period_list, company_currency, net_change_in_cash, filters)
columns = get_columns(
filters.periodicity,
period_list,
filters.accumulated_values,
filters.company,
True,
)
chart = get_chart_data(columns, data, company_currency)
@@ -255,6 +270,137 @@ def add_total_row_account(out, data, label, period_list, currency, summary_data,
out.append(total_row)
out.append({})
return total_row
def show_opening_and_closing_balance(out, period_list, currency, net_change_in_cash, filters):
opening_balance = {
"section_name": "Opening",
"section": "Opening",
"currency": currency,
}
closing_balance = {
"section_name": "Closing (Opening + Total)",
"section": "Closing (Opening + Total)",
"currency": currency,
}
opening_amount = get_opening_balance(filters.company, period_list, filters) or 0.0
running_total = opening_amount
for i, period in enumerate(period_list):
key = period["key"]
change = net_change_in_cash.get(key, 0.0)
opening_balance[key] = opening_amount if i == 0 else running_total
running_total += change
closing_balance[key] = running_total
opening_balance["total"] = opening_balance[period_list[0]["key"]]
closing_balance["total"] = closing_balance[period_list[-1]["key"]]
out.extend([opening_balance, net_change_in_cash, closing_balance, {}])
def get_opening_balance(company, period_list, filters):
from copy import deepcopy
cash_value = {}
account_types = get_cash_flow_accounts()
net_profit_loss = 0.0
local_filters = deepcopy(filters)
local_filters.start_date, local_filters.end_date = get_opening_range_using_fiscal_year(
company, period_list
)
for section in account_types:
section_name = section.get("section_name")
cash_value.setdefault(section_name, 0.0)
if section_name == "Operations":
net_profit_loss += get_net_income(company, period_list, local_filters)
for account in section.get("account_types", []):
account_type = account.get("account_type")
local_filters.account_type = account_type
amount = get_account_type_based_gl_data(company, local_filters) or 0.0
if account_type == "Depreciation":
cash_value[section_name] += amount * -1
else:
cash_value[section_name] += amount
return sum(cash_value.values()) + net_profit_loss
def get_net_income(company, period_list, filters):
gl_entries_by_account_for_income, gl_entries_by_account_for_expense = {}, {}
income, expense = 0.0, 0.0
from_date, to_date = get_opening_range_using_fiscal_year(company, period_list)
for root_type in ["Income", "Expense"]:
for root in frappe.db.sql(
"""select lft, rgt from tabAccount
where root_type=%s and ifnull(parent_account, '') = ''""",
root_type,
as_dict=1,
):
set_gl_entries_by_account(
company,
from_date,
to_date,
filters,
gl_entries_by_account_for_income
if root_type == "Income"
else gl_entries_by_account_for_expense,
root.lft,
root.rgt,
root_type=root_type,
ignore_closing_entries=True,
)
for entries in gl_entries_by_account_for_income.values():
for entry in entries:
if entry.posting_date <= to_date:
amount = (entry.debit - entry.credit) * -1
income = flt((income + amount), 2)
for entries in gl_entries_by_account_for_expense.values():
for entry in entries:
if entry.posting_date <= to_date:
amount = entry.debit - entry.credit
expense = flt((expense + amount), 2)
return income - expense
def get_opening_range_using_fiscal_year(company, period_list):
first_from_date = period_list[0]["from_date"]
previous_day = first_from_date - timedelta(days=1)
# Get the earliest fiscal year for the company
FiscalYear = DocType("Fiscal Year")
FiscalYearCompany = DocType("Fiscal Year Company")
earliest_fy = (
frappe.qb.from_(FiscalYear)
.join(FiscalYearCompany)
.on(FiscalYearCompany.parent == FiscalYear.name)
.select(FiscalYear.year_start_date)
.where(FiscalYearCompany.company == company)
.orderby(FiscalYear.year_start_date, order=Order.asc)
.limit(1)
).run(as_dict=True)
if not earliest_fy:
frappe.throw(_("Not able to find the earliest Fiscal Year for the given company."))
company_start_date = earliest_fy[0]["year_start_date"]
return company_start_date, previous_day
def get_report_summary(summary_data, currency):
report_summary = []
@@ -276,7 +422,7 @@ def get_chart_data(columns, data, currency):
for section in data
if section.get("parent_section") is None and section.get("currency")
]
datasets = datasets[:-1]
datasets = datasets[:-2]
chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"}

View File

@@ -275,12 +275,25 @@ class PartyLedgerSummaryReport:
if gle.posting_date < self.filters.from_date or gle.is_opening == "Yes":
self.party_data[gle.party].opening_balance += amount
else:
if amount > 0:
self.party_data[gle.party].invoiced_amount += amount
elif gle.voucher_no in self.return_invoices:
self.party_data[gle.party].return_amount -= amount
# Cache the party data reference to avoid repeated dictionary lookups
party_data = self.party_data[gle.party]
# Check if this is a direct return invoice (most specific condition first)
if gle.voucher_no in self.return_invoices:
party_data.return_amount -= amount
# Check if this entry is against a return invoice
elif gle.against_voucher in self.return_invoices:
# For entries against return invoices, positive amounts are payments
if amount > 0:
party_data.paid_amount -= amount
else:
party_data.invoiced_amount += amount
# Normal transaction logic
else:
self.party_data[gle.party].paid_amount -= amount
if amount > 0:
party_data.invoiced_amount += amount
else:
party_data.paid_amount -= amount
out = []
for party, row in self.party_data.items():
@@ -289,7 +302,7 @@ class PartyLedgerSummaryReport:
or row.invoiced_amount
or row.paid_amount
or row.return_amount
or row.closing_amount
or row.closing_balance # Fixed typo from closing_amount to closing_balance
):
total_party_adjustment = sum(
amount for amount in self.party_adjustment_details.get(party, {}).values()
@@ -313,6 +326,7 @@ class PartyLedgerSummaryReport:
gle.party,
gle.voucher_type,
gle.voucher_no,
gle.against_voucher, # For handling returned invoices (Credit/Debit Notes)
gle.debit,
gle.credit,
gle.is_opening,

View File

@@ -150,3 +150,157 @@ class TestCustomerLedgerSummary(FrappeTestCase, AccountsTestMixin):
for field in expected_after_cr_and_payment:
with self.subTest(field=field):
self.assertEqual(report[0].get(field), expected_after_cr_and_payment.get(field))
def test_journal_voucher_against_return_invoice(self):
filters = {"company": self.company, "from_date": today(), "to_date": today()}
# Create Sales Invoice of 10 qty at rate 100 (Amount: 1000.0)
si1 = self.create_sales_invoice(do_not_submit=True)
si1.save().submit()
expected = {
"party": "_Test Customer",
"party_name": "_Test Customer",
"opening_balance": 0,
"invoiced_amount": 1000.0,
"paid_amount": 0,
"return_amount": 0,
"closing_balance": 1000.0,
"currency": "INR",
"customer_name": "_Test Customer",
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
for field in expected:
with self.subTest(field=field):
actual_value = report[0].get(field)
expected_value = expected.get(field)
self.assertEqual(
actual_value,
expected_value,
f"Field {field} does not match expected value. "
f"Expected: {expected_value}, Got: {actual_value}",
)
# Create Payment Entry (Receive) for the first invoice
pe1 = self.create_payment_entry(si1.name, True)
pe1.paid_amount = 1000 # Full payment 1000.0
pe1.save().submit()
expected_after_payment = {
"party": "_Test Customer",
"party_name": "_Test Customer",
"opening_balance": 0,
"invoiced_amount": 1000.0,
"paid_amount": 1000.0,
"return_amount": 0,
"closing_balance": 0.0,
"currency": "INR",
"customer_name": "_Test Customer",
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
for field in expected_after_payment:
with self.subTest(field=field):
actual_value = report[0].get(field)
expected_value = expected_after_payment.get(field)
self.assertEqual(
actual_value,
expected_value,
f"Field {field} does not match expected value. "
f"Expected: {expected_value}, Got: {actual_value}",
)
# Create Credit Note (return invoice) for first invoice (1000.0)
cr_note = self.create_credit_note(si1.name, do_not_submit=True)
cr_note.items[0].qty = -10 # 1 item of qty 10 at rate 100 (Amount: 1000.0)
cr_note.save().submit()
expected_after_cr_note = {
"party": "_Test Customer",
"party_name": "_Test Customer",
"opening_balance": 0,
"invoiced_amount": 1000.0,
"paid_amount": 1000.0,
"return_amount": 1000.0,
"closing_balance": -1000.0,
"currency": "INR",
"customer_name": "_Test Customer",
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
for field in expected_after_cr_note:
with self.subTest(field=field):
actual_value = report[0].get(field)
expected_value = expected_after_cr_note.get(field)
self.assertEqual(
actual_value,
expected_value,
f"Field {field} does not match expected value. "
f"Expected: {expected_value}, Got: {actual_value}",
)
# Create Payment Entry for the returned amount (1000.0) - Pay the customer back
pe2 = get_payment_entry("Sales Invoice", cr_note.name, bank_account=self.cash)
pe2.insert().submit()
expected_after_cr_and_return_payment = {
"party": "_Test Customer",
"party_name": "_Test Customer",
"opening_balance": 0,
"invoiced_amount": 1000.0,
"paid_amount": 0,
"return_amount": 1000.0,
"closing_balance": 0,
"currency": "INR",
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
for field in expected_after_cr_and_return_payment:
with self.subTest(field=field):
actual_value = report[0].get(field)
expected_value = expected_after_cr_and_return_payment.get(field)
self.assertEqual(
actual_value,
expected_value,
f"Field {field} does not match expected value. "
f"Expected: {expected_value}, Got: {actual_value}",
)
# Create second Sales Invoice of 10 qty at rate 100 (Amount: 1000.0)
si2 = self.create_sales_invoice(do_not_submit=True)
si2.save().submit()
# Create Payment Entry (Receive) for the second invoice - payment (500.0)
pe3 = self.create_payment_entry(si2.name, True)
pe3.paid_amount = 500 # Partial payment 500.0
pe3.save().submit()
expected_after_cr_and_payment = {
"party": "_Test Customer",
"party_name": "_Test Customer",
"opening_balance": 0.0,
"invoiced_amount": 2000.0,
"paid_amount": 500.0,
"return_amount": 1000.0,
"closing_balance": 500.0,
"currency": "INR",
"customer_name": "_Test Customer",
}
report = execute(filters)[1]
self.assertEqual(len(report), 1)
for field in expected_after_cr_and_payment:
with self.subTest(field=field):
actual_value = report[0].get(field)
expected_value = expected_after_cr_and_payment.get(field)
self.assertEqual(
actual_value,
expected_value,
f"Field {field} does not match expected value. "
f"Expected: {expected_value}, Got: {actual_value}",
)

View File

@@ -86,7 +86,7 @@ def set_gl_entries_by_account(dimension_list, filters, account, gl_entries_by_ac
"finance_book": cstr(filters.get("finance_book")),
}
gl_filters["dimensions"] = set(dimension_list)
gl_filters["dimensions"] = tuple(set(dimension_list))
if filters.get("include_default_book_entries"):
gl_filters["company_fb"] = frappe.get_cached_value("Company", filters.company, "default_finance_book")

View File

@@ -197,6 +197,11 @@ frappe.query_reports["General Ledger"] = {
label: __("Show Net Values in Party Account"),
fieldtype: "Check",
},
{
fieldname: "show_amount_in_company_currency",
label: __("Show Credit / Debit in Company Currency"),
fieldtype: "Check",
},
{
fieldname: "add_values_in_transaction_currency",
label: __("Add Columns in Transaction Currency"),

View File

@@ -1,29 +1,34 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-12-06 13:22:23",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:17:51.995451",
"modified_by": "Administrator",
"module": "Accounts",
"name": "General Ledger",
"owner": "Administrator",
"ref_doctype": "GL Entry",
"report_name": "General Ledger",
"report_type": "Script Report",
"add_total_row": 1,
"add_translate_data": 0,
"columns": [],
"creation": "2013-12-06 13:22:23",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 3,
"is_standard": "Yes",
"letterhead": null,
"modified": "2025-08-13 12:47:27.645023",
"modified_by": "Administrator",
"module": "Accounts",
"name": "General Ledger",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "GL Entry",
"report_name": "General Ledger",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
},
{
"role": "Accounts Manager"
},
},
{
"role": "Auditor"
}
]
}
],
"timeout": 0
}

View File

@@ -203,8 +203,14 @@ def get_gl_entries(filters, accounting_dimensions):
as_dict=1,
)
party_name_map = get_party_name_map()
for gl_entry in gl_entries:
if gl_entry.party_type and gl_entry.party:
gl_entry.party_name = party_name_map.get(gl_entry.party_type, {}).get(gl_entry.party)
if filters.get("presentation_currency"):
return convert_to_presentation_currency(gl_entries, currency_map)
return convert_to_presentation_currency(gl_entries, currency_map, filters)
else:
return gl_entries
@@ -337,6 +343,20 @@ def get_conditions(filters):
return "and {}".format(" and ".join(conditions)) if conditions else ""
def get_party_name_map():
party_map = {}
customers = frappe.get_all("Customer", fields=["name", "customer_name"])
party_map["Customer"] = {c.name: c.customer_name for c in customers}
suppliers = frappe.get_all("Supplier", fields=["name", "supplier_name"])
party_map["Supplier"] = {s.name: s.supplier_name for s in suppliers}
employees = frappe.get_all("Employee", fields=["name", "employee_name"])
party_map["Employee"] = {e.name: e.employee_name for e in employees}
return party_map
def get_accounts_with_children(accounts):
if not isinstance(accounts, list):
accounts = [d.strip() for d in accounts.strip().split(",") if d]
@@ -605,6 +625,18 @@ def get_columns(filters):
company = filters.get("company") or get_default_company()
filters["presentation_currency"] = currency = get_company_currency(company)
company_currency = get_company_currency(filters.get("company") or get_default_company())
if (
filters.get("show_amount_in_company_currency")
and filters["presentation_currency"] != company_currency
):
frappe.throw(
_(
f'Presentation Currency cannot be {frappe.bold(filters["presentation_currency"])} , When {frappe.bold("Show Credit / Debit in Company Currency")} is enabled.'
)
)
columns = [
{
"label": _("GL Entry"),
@@ -689,6 +721,19 @@ def get_columns(filters):
{"label": _("Party"), "fieldname": "party", "width": 100},
]
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
if supplier_master_name != "Supplier Name" or customer_master_name != "Customer Name":
columns.append(
{
"label": _("Party Name"),
"fieldname": "party_name",
"fieldtype": "Data",
"width": 150,
}
)
if filters.get("include_dimensions"):
columns.append({"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100})

View File

@@ -33,6 +33,7 @@ def execute(filters=None):
"invoice_or_item",
"customer",
"customer_group",
"customer_name",
"posting_date",
"item_code",
"item_name",
@@ -95,6 +96,7 @@ def execute(filters=None):
"customer": [
"customer",
"customer_group",
"customer_name",
"qty",
"base_rate",
"buying_rate",
@@ -250,6 +252,10 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
def get_columns(group_wise_columns, filters):
columns = []
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
column_map = frappe._dict(
{
"parent": {
@@ -395,6 +401,12 @@ def get_columns(group_wise_columns, filters):
"options": "Customer Group",
"width": 100,
},
"customer_name": {
"label": _("Customer Name"),
"fieldname": "customer_name",
"fieldtype": "Data",
"width": 150,
},
"territory": {
"label": _("Territory"),
"fieldname": "territory",
@@ -419,6 +431,10 @@ def get_columns(group_wise_columns, filters):
)
for col in group_wise_columns.get(scrub(filters.group_by)):
if col == "customer_name" and (
supplier_master_name == "Supplier Name" and customer_master_name == "Customer Name"
):
continue
columns.append(column_map.get(col))
columns.append(
@@ -440,6 +456,7 @@ def get_column_names():
"invoice_or_item": "sales_invoice",
"customer": "customer",
"customer_group": "customer_group",
"customer_name": "customer_name",
"posting_date": "posting_date",
"item_code": "item_code",
"item_name": "item_name",
@@ -905,7 +922,7 @@ class GrossProfitGenerator:
`tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time,
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group,
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group, `tabSales Invoice`.customer_name,
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
`tabSales Invoice`.base_net_total as "invoice_base_net_total",
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
@@ -1003,6 +1020,7 @@ class GrossProfitGenerator:
"update_stock": row.update_stock,
"customer": row.customer,
"customer_group": row.customer_group,
"customer_name": row.customer_name,
"item_code": None,
"item_name": None,
"description": None,
@@ -1032,6 +1050,7 @@ class GrossProfitGenerator:
"project": row.project,
"customer": row.customer,
"customer_group": row.customer_group,
"customer_name": row.customer_name,
"item_code": item.item_code,
"item_name": item.item_name,
"description": item.description,

View File

@@ -178,7 +178,7 @@ def get_columns(additional_table_columns, filters):
"fieldname": "invoice",
"fieldtype": "Link",
"options": "Purchase Invoice",
"width": 120,
"width": 150,
},
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
]
@@ -310,8 +310,8 @@ def apply_conditions(query, pi, pii, filters):
def get_items(filters, additional_table_columns):
doctype = "Purchase Invoice"
pi = frappe.qb.DocType(doctype)
pii = frappe.qb.DocType(f"{doctype} Item")
pi = frappe.qb.DocType("Purchase Invoice")
pii = frappe.qb.DocType("Purchase Invoice Item")
Item = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(pi)
@@ -331,6 +331,7 @@ def get_items(filters, additional_table_columns):
pi.unrealized_profit_loss_account,
pii.item_code,
pii.description,
pii.item_name,
pii.item_group,
pii.item_name.as_("pi_item_name"),
pii.item_group.as_("pi_item_group"),
@@ -374,7 +375,7 @@ def get_items(filters, additional_table_columns):
if match_conditions:
query += " and " + match_conditions
query = apply_order_by_conditions(query, pi, pii, filters)
query = apply_order_by_conditions(doctype, query, filters)
return frappe.db.sql(query, params, as_dict=True)

View File

@@ -198,7 +198,7 @@ def get_columns(additional_table_columns, filters):
"fieldname": "invoice",
"fieldtype": "Link",
"options": "Sales Invoice",
"width": 120,
"width": 150,
},
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
]
@@ -343,7 +343,7 @@ def get_columns(additional_table_columns, filters):
return columns
def apply_conditions(query, si, sii, filters, additional_conditions=None):
def apply_conditions(query, si, sii, sip, filters, additional_conditions=None):
for opts in ("company", "customer"):
if filters.get(opts):
query = query.where(si[opts] == filters[opts])
@@ -355,10 +355,13 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None):
query = query.where(si.posting_date <= filters.get("to_date"))
if filters.get("mode_of_payment"):
sales_invoice = frappe.db.get_all(
"Sales Invoice Payment", {"mode_of_payment": filters.get("mode_of_payment")}, pluck="parent"
subquery = (
frappe.qb.from_(sip)
.select(sip.parent)
.where(sip.mode_of_payment == filters.get("mode_of_payment"))
.groupby(sip.parent)
)
query = query.where(si.name.isin(sales_invoice))
query = query.where(si.name.isin(subquery))
if filters.get("warehouse"):
if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
@@ -397,15 +400,18 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None):
return query
def apply_order_by_conditions(query, si, ii, filters):
def apply_order_by_conditions(doctype, query, filters):
invoice = f"`tab{doctype}`"
invoice_item = f"`tab{doctype} Item`"
if not filters.get("group_by"):
query += f" order by {si.posting_date} desc, {ii.item_group} desc"
query += f" order by {invoice}.posting_date desc, {invoice_item}.item_group desc"
elif filters.get("group_by") == "Invoice":
query += f" order by {ii.parent} desc"
query += f" order by {invoice_item}.parent desc"
elif filters.get("group_by") == "Item":
query += f" order by {ii.item_code}"
query += f" order by {invoice_item}.item_code"
elif filters.get("group_by") == "Item Group":
query += f" order by {ii.item_group}"
query += f" order by {invoice_item}.item_group"
elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"):
filter_field = frappe.scrub(filters.get("group_by"))
query += f" order by {filter_field} desc"
@@ -415,8 +421,9 @@ def apply_order_by_conditions(query, si, ii, filters):
def get_items(filters, additional_query_columns, additional_conditions=None):
doctype = "Sales Invoice"
si = frappe.qb.DocType(doctype)
sii = frappe.qb.DocType(f"{doctype} Item")
si = frappe.qb.DocType("Sales Invoice")
sii = frappe.qb.DocType("Sales Invoice Item")
sip = frappe.qb.DocType("Sales Invoice Payment")
item = frappe.qb.DocType("Item")
query = (
@@ -481,17 +488,17 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
if filters.get("customer_group"):
query = query.where(si.customer_group == filters["customer_group"])
query = apply_conditions(query, si, sii, filters, additional_conditions)
query = apply_conditions(query, si, sii, sip, filters, additional_conditions)
from frappe.desk.reportview import build_match_conditions
query, params = query.walk()
match_conditions = build_match_conditions("Sales Invoice")
match_conditions = build_match_conditions(doctype)
if match_conditions:
query += " and " + match_conditions
query = apply_order_by_conditions(query, si, sii, filters)
query = apply_order_by_conditions(doctype, query, filters)
return frappe.db.sql(query, params, as_dict=True)
@@ -763,25 +770,13 @@ def add_total_row(
def get_display_value(filters, group_by_field, item):
if filters.get("group_by") == "Item":
if item.get("item_code") != item.get("item_name"):
value = (
cstr(item.get("item_code"))
+ "<br><br>"
+ "<span style='font-weight: normal'>"
+ cstr(item.get("item_name"))
+ "</span>"
)
value = f"{item.get('item_code')}: {item.get('item_name')}"
else:
value = item.get("item_code", "")
elif filters.get("group_by") in ("Customer", "Supplier"):
party = frappe.scrub(filters.get("group_by"))
if item.get(party) != item.get(party + "_name"):
value = (
item.get(party)
+ "<br><br>"
+ "<span style='font-weight: normal'>"
+ item.get(party + "_name")
+ "</span>"
)
value = f"{item.get(party)}: {item.get(party + '_name')}"
else:
value = item.get(party)
else:

View File

@@ -46,6 +46,7 @@ def get_ordered_to_be_billed_data(args, filters=None):
child_doctype.item_name,
child_doctype.description,
project_field,
doctype.company,
)
.where(
(doctype.docstatus == 1)

View File

@@ -46,6 +46,7 @@ class PaymentLedger:
against_voucher_no=ple.against_voucher_no,
amount=ple.amount,
currency=ple.account_currency,
company=ple.company,
)
if self.filters.include_account_currency:
@@ -77,6 +78,7 @@ class PaymentLedger:
against_voucher_no="Outstanding:",
amount=total,
currency=voucher_data[0].currency,
company=voucher_data[0].company,
)
if self.filters.include_account_currency:
@@ -85,7 +87,12 @@ class PaymentLedger:
voucher_data.append(entry)
# empty row
voucher_data.append(frappe._dict())
voucher_data.append(
frappe._dict(
currency=voucher_data[0].currency,
company=voucher_data[0].company,
)
)
self.data.extend(voucher_data)
def build_conditions(self):
@@ -130,7 +137,6 @@ class PaymentLedger:
)
def get_columns(self):
company_currency = frappe.get_cached_value("Company", self.filters.get("company"), "default_currency")
options = None
self.columns.append(
dict(
@@ -195,7 +201,7 @@ class PaymentLedger:
label=_("Amount"),
fieldname="amount",
fieldtype="Currency",
options=company_currency,
options="Company:company:default_currency",
width="100",
)
)

View File

@@ -1,21 +1,22 @@
{
"add_total_row": 0,
"add_translate_data": 0,
"columns": [],
"creation": "2013-05-06 12:28:23",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 3,
"idx": 6,
"is_standard": "Yes",
"modified": "2021-10-06 06:26:07.881340",
"letterhead": null,
"modified": "2025-07-17 23:16:19.892044",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Partners Commission",
"owner": "Administrator",
"prepared_report": 0,
"query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:220\",\n\tsum(base_net_total) as \"Invoiced Amount (Excl. Tax):Currency:220\",\n\tsum(amount_eligible_for_commission) as \"Amount Eligible for Commission:Currency:220\",\n\tsum(total_commission) as \"Total Commission:Currency:170\",\n\tsum(total_commission)*100/sum(amount_eligible_for_commission) as \"Average Commission Rate:Percent:220\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"",
"query": "SELECT\n sales_partner as \"Sales Partner:Link / Sales Partner:220\",\n sum(base_net_total) as \"Invoiced Amount (Excl. Tax):Currency:220\",\n sum(amount_eligible_for_commission) as \"Amount Eligible for Commission:Currency:220\",\n sum(total_commission) as \"Total Commission:Currency:170\",\n sum(total_commission)*100 / sum(amount_eligible_for_commission) as \"Average Commission Rate:Percent:220\"\nFROM\n (\n SELECT\n sales_partner,\n base_net_total,\n total_commission,\n amount_eligible_for_commission\n FROM\n `tabSales Invoice` \n WHERE\n docstatus = 1\n AND IFNULL(base_net_total, 0) > 0\n AND IFNULL(total_commission, 0) > 0\n\n UNION ALL\n\n SELECT\n sales_partner,\n base_net_total,\n total_commission,\n amount_eligible_for_commission\n FROM\n `tabPOS Invoice`\n WHERE\n docstatus = 1\n AND IFNULL(base_net_total, 0) > 0\n AND IFNULL(total_commission, 0) > 0\n ) AS sub\nGROUP BY\n sales_partner\nORDER BY\n \"Total Commission:Currency:120\"",
"ref_doctype": "Sales Invoice",
"report_name": "Sales Partners Commission",
"report_type": "Query Report",
@@ -26,5 +27,6 @@
{
"role": "Accounts User"
}
]
}
],
"timeout": 0
}

View File

@@ -45,6 +45,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
gle_map = get_gle_map(tds_docs)
out = []
entries = {}
for name, details in gle_map.items():
for entry in details:
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
@@ -119,8 +120,13 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
"supplier_invoice_date": bill_date,
}
)
out.append(row)
key = entry.voucher_no
if key in entries:
entries[key]["tax_amount"] += tax_amount
else:
entries[key] = row
out = list(entries.values())
out.sort(key=lambda x: (x["section_code"], x["transaction_date"]))
return out

View File

@@ -86,7 +86,7 @@ def get_rate_as_at(date, from_currency, to_currency):
return rate
def convert_to_presentation_currency(gl_entries, currency_info):
def convert_to_presentation_currency(gl_entries, currency_info, filters=None):
"""
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
in `currency_info`.
@@ -99,6 +99,13 @@ def convert_to_presentation_currency(gl_entries, currency_info):
company_currency = currency_info["company_currency"]
account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
exchange_gain_or_loss = False
if filters and isinstance(filters.get("account"), list):
account_filter = filters.get("account")
gain_loss_account = frappe.db.get_value("Company", filters.company, "exchange_gain_loss_account")
exchange_gain_or_loss = len(account_filter) == 1 and account_filter[0] == gain_loss_account
for entry in gl_entries:
debit = flt(entry["debit"])
@@ -107,7 +114,11 @@ def convert_to_presentation_currency(gl_entries, currency_info):
credit_in_account_currency = flt(entry["credit_in_account_currency"])
account_currency = entry["account_currency"]
if len(account_currencies) == 1 and account_currency == presentation_currency:
if (
len(account_currencies) == 1
and account_currency == presentation_currency
and not exchange_gain_or_loss
) and not (filters and filters.get("show_amount_in_company_currency")):
entry["debit"] = debit_in_account_currency
entry["credit"] = credit_in_account_currency
else:

View File

@@ -2,12 +2,14 @@
# License: GNU General Public License v3. See license.txt
from collections import defaultdict
from json import loads
from typing import TYPE_CHECKING, Optional
import frappe
import frappe.defaults
from frappe import _, qb, throw
from frappe.desk.reportview import build_match_conditions
from frappe.model.meta import get_field_precision
from frappe.query_builder import AliasedQuery, Case, Criterion, Table
from frappe.query_builder.functions import Count, Max, Sum
@@ -26,6 +28,7 @@ from frappe.utils import (
nowdate,
)
from pypika import Order
from pypika.functions import Coalesce
from pypika.terms import ExistsCriterion
import erpnext
@@ -467,65 +470,46 @@ def reconcile_against_document(
reconciled_entries[(row.voucher_type, row.voucher_no)] = []
reconciled_entries[(row.voucher_type, row.voucher_no)].append(row)
for key, entries in reconciled_entries.items():
voucher_type = key[0]
voucher_no = key[1]
voucher_type, voucher_no = key
# cancel advance entry
doc = frappe.get_doc(voucher_type, voucher_no)
frappe.flags.ignore_party_validation = True
# When Advance is allocated from an Order to an Invoice
# whole ledger must be reposted
repost_whole_ledger = any([x.voucher_detail_no for x in entries])
if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
if repost_whole_ledger:
doc.make_gl_entries(cancel=1)
else:
doc.make_advance_gl_entries(cancel=1)
else:
_delete_pl_entries(voucher_type, voucher_no)
reposting_rows = []
for entry in entries:
check_if_advance_entry_modified(entry)
validate_allocated_amount(entry)
dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions)
# update ref in advance entry
if voucher_type == "Journal Entry":
referenced_row, update_advance_paid = update_reference_in_journal_entry(
entry, doc, do_not_save=False
)
referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False)
# advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss
# amount and account in args
# referenced_row is used to deduplicate gain/loss journal
entry.update({"referenced_row": referenced_row})
entry.update({"referenced_row": referenced_row.name})
doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
else:
referenced_row, update_advance_paid = update_reference_in_payment_entry(
referenced_row = update_reference_in_payment_entry(
entry,
doc,
do_not_save=True,
skip_ref_details_update_for_pe=skip_ref_details_update_for_pe,
dimensions_dict=dimensions_dict,
)
if referenced_row.get("outstanding_amount"):
referenced_row.outstanding_amount -= flt(entry.allocated_amount)
reposting_rows.append(referenced_row)
doc.save(ignore_permissions=True)
# re-submit advance entry
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
# When Advance is allocated from an Order to an Invoice
# whole ledger must be reposted
if repost_whole_ledger:
doc.make_gl_entries()
else:
# both ledgers must be posted to for `Advance` in separate account feature
# TODO: find a more efficient way post only for the new linked vouchers
doc.make_advance_gl_entries()
for row in reposting_rows:
doc.make_advance_gl_entries(entry=row)
else:
_delete_pl_entries(voucher_type, voucher_no)
gl_map = doc.build_gl_map()
# Make sure there is no overallocation
from erpnext.accounts.general_ledger import process_debit_credit_difference
@@ -542,11 +526,6 @@ def reconcile_against_document(
entry.party_type,
entry.party,
)
# update advance paid in Advance Receivable/Payable doctypes
if update_advance_paid:
for t, n in update_advance_paid:
frappe.get_doc(t, n).set_total_advance_paid()
frappe.flags.ignore_party_validation = False
@@ -627,12 +606,6 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
"""
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
# Update Advance Paid in SO/PO since they might be getting unlinked
update_advance_paid = []
if jv_detail.get("reference_type") in ["Sales Order", "Purchase Order"]:
update_advance_paid.append((jv_detail.reference_type, jv_detail.reference_name))
rev_dr_or_cr = (
"debit_in_account_currency"
if d["dr_or_cr"] == "credit_in_account_currency"
@@ -685,6 +658,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
new_row.is_advance = cstr(jv_detail.is_advance)
new_row.docstatus = 1
if jv_detail.get("reference_type") in get_advance_payment_doctypes():
new_row.advance_voucher_type = jv_detail.get("reference_type")
new_row.advance_voucher_no = jv_detail.get("reference_name")
# will work as update after submit
journal_entry.flags.ignore_validate_update_after_submit = True
# Ledgers will be reposted by Reconciliation tool
@@ -692,7 +669,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
if not do_not_save:
journal_entry.save(ignore_permissions=True)
return new_row.name, update_advance_paid
return new_row
def update_reference_in_payment_entry(
@@ -711,20 +688,19 @@ def update_reference_in_payment_entry(
"account": d.account,
"dimensions": d.dimensions,
}
update_advance_paid = []
advance_payment_doctypes = get_advance_payment_doctypes()
# Update Reconciliation effect date in reference
if payment_entry.book_advance_payments_in_separate_party_account:
reconcile_on = get_reconciliation_effect_date(d, payment_entry.company, payment_entry.posting_date)
reconcile_on = get_reconciliation_effect_date(
d.against_voucher_type, d.against_voucher, payment_entry.company, payment_entry.posting_date
)
reference_details.update({"reconcile_effect_on": reconcile_on})
if d.voucher_detail_no:
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
# Update Advance Paid in SO/PO since they are getting unlinked
if existing_row.get("reference_doctype") in ["Sales Order", "Purchase Order"]:
update_advance_paid.append((existing_row.reference_doctype, existing_row.reference_name))
if d.allocated_amount <= existing_row.allocated_amount:
existing_row.allocated_amount -= d.allocated_amount
@@ -732,7 +708,13 @@ def update_reference_in_payment_entry(
new_row.docstatus = 1
for field in list(reference_details):
new_row.set(field, reference_details[field])
if existing_row.reference_doctype in advance_payment_doctypes:
new_row.advance_voucher_type = existing_row.reference_doctype
new_row.advance_voucher_no = existing_row.reference_name
row = new_row
else:
new_row = payment_entry.append("references")
new_row.docstatus = 1
@@ -766,23 +748,25 @@ def update_reference_in_payment_entry(
payment_entry.flags.ignore_reposting_on_reconciliation = True
if not do_not_save:
payment_entry.save(ignore_permissions=True)
return row, update_advance_paid
return row
def get_reconciliation_effect_date(reference, company, posting_date):
def get_reconciliation_effect_date(against_voucher_type, against_voucher, company, posting_date):
reconciliation_takes_effect_on = frappe.get_cached_value(
"Company", company, "reconciliation_takes_effect_on"
)
# default
reconcile_on = posting_date
if reconciliation_takes_effect_on == "Advance Payment Date":
reconcile_on = posting_date
elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
date_field = "posting_date"
if reference.against_voucher_type in ["Sales Order", "Purchase Order"]:
if against_voucher_type in ["Sales Order", "Purchase Order"]:
date_field = "transaction_date"
reconcile_on = frappe.db.get_value(
reference.against_voucher_type, reference.against_voucher, date_field
)
reconcile_on = frappe.db.get_value(against_voucher_type, against_voucher, date_field)
if getdate(reconcile_on) < getdate(posting_date):
reconcile_on = posting_date
elif reconciliation_takes_effect_on == "Reconciliation Date":
@@ -943,6 +927,24 @@ def update_accounting_ledgers_after_reference_removal(
ple_update_query = ple_update_query.where(ple.voucher_no == payment_name)
ple_update_query.run()
# Advance Payment
adv = qb.DocType("Advance Payment Ledger Entry")
adv_ple = (
qb.update(adv)
.set(adv.delinked, 1)
.set(adv.modified, now())
.set(adv.modified_by, frappe.session.user)
.where(adv.delinked == 0)
.where(
((adv.against_voucher_type == ref_type) & (adv.against_voucher_no == ref_no))
| ((adv.voucher_type == ref_type) & (adv.voucher_no == ref_no))
)
)
if payment_name:
adv_ple = adv_ple.where(adv.voucher_no == payment_name)
adv_ple.run()
def remove_ref_from_advance_section(ref_doc: object = None):
# TODO: this might need some testing
@@ -979,6 +981,8 @@ def remove_ref_doc_link_from_jv(
qb.update(jea)
.set(jea.reference_type, None)
.set(jea.reference_name, None)
.set(jea.advance_voucher_type, None)
.set(jea.advance_voucher_no, None)
.set(jea.modified, now())
.set(jea.modified_by, frappe.session.user)
.where((jea.reference_type == ref_type) & (jea.reference_name == ref_no))
@@ -1335,6 +1339,7 @@ def create_payment_gateway_account(gateway, payment_channel="Email"):
"payment_account": bank_account.name,
"currency": bank_account.account_currency,
"payment_channel": payment_channel,
"company": company,
}
).insert(ignore_permissions=True, ignore_if_duplicate=True)
@@ -1489,6 +1494,11 @@ def _delete_pl_entries(voucher_type, voucher_no):
qb.from_(ple).delete().where((ple.voucher_type == voucher_type) & (ple.voucher_no == voucher_no)).run()
def _delete_adv_pl_entries(voucher_type, voucher_no):
adv = qb.DocType("Advance Payment Ledger Entry")
qb.from_(adv).delete().where((adv.voucher_type == voucher_type) & (adv.voucher_no == voucher_no)).run()
def _delete_gl_entries(voucher_type, voucher_no):
gle = qb.DocType("GL Entry")
qb.from_(gle).delete().where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)).run()
@@ -1794,6 +1804,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
dr_or_cr = 0
account_type = None
for gle in gl_entries:
if gle.account in receivable_or_payable_accounts:
account_type = get_account_type(gle.account)
@@ -1808,6 +1819,11 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
dr_or_cr *= -1
dr_or_cr_account_currency *= -1
against_voucher_type = (
gle.against_voucher_type if gle.against_voucher_type else gle.voucher_type
)
against_voucher_no = gle.against_voucher if gle.against_voucher else gle.voucher_no
ple = frappe._dict(
doctype="Payment Ledger Entry",
posting_date=gle.posting_date,
@@ -1822,14 +1838,12 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
voucher_type=gle.voucher_type,
voucher_no=gle.voucher_no,
voucher_detail_no=gle.voucher_detail_no,
against_voucher_type=gle.against_voucher_type
if gle.against_voucher_type
else gle.voucher_type,
against_voucher_no=gle.against_voucher if gle.against_voucher else gle.voucher_no,
against_voucher_type=against_voucher_type,
against_voucher_no=against_voucher_no,
account_currency=gle.account_currency,
amount=dr_or_cr,
amount_in_account_currency=dr_or_cr_account_currency,
delinked=True if cancel else False,
delinked=cancel,
remarks=gle.remarks,
)
@@ -1838,10 +1852,40 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
for dimension in dimensions_and_defaults[0]:
ple[dimension.fieldname] = gle.get(dimension.fieldname)
if gle.advance_voucher_no:
# create advance entry
adv = get_advance_ledger_entry(
gle, against_voucher_type, against_voucher_no, dr_or_cr_account_currency, cancel
)
ple_map.append(adv)
ple_map.append(ple)
return ple_map
def get_advance_ledger_entry(gle, against_voucher_type, against_voucher_no, amount, cancel):
event = (
"Submit"
if (against_voucher_type == gle.voucher_type and against_voucher_no == gle.voucher_no)
else "Adjustment"
)
return frappe._dict(
doctype="Advance Payment Ledger Entry",
company=gle.company,
voucher_type=gle.voucher_type,
voucher_no=gle.voucher_no,
voucher_detail_no=gle.voucher_detail_no,
against_voucher_type=gle.advance_voucher_type,
against_voucher_no=gle.advance_voucher_no,
amount=amount,
currency=gle.account_currency,
event=event,
delinked=cancel,
)
def create_payment_ledger_entry(
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
):
@@ -1862,6 +1906,16 @@ def create_payment_ledger_entry(
def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party):
from erpnext.accounts.doctype.dunning.dunning import update_linked_dunnings
if not voucher_type or not voucher_no:
return
if voucher_type in get_advance_payment_doctypes():
ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no)
ref_doc.set_total_advance_paid()
return
ple = frappe.qb.DocType("Payment Ledger Entry")
vouchers = [frappe._dict({"voucher_type": voucher_type, "voucher_no": voucher_no})]
common_filter = []
@@ -1886,6 +1940,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
):
outstanding = voucher_outstanding[0]
ref_doc = frappe.get_doc(voucher_type, voucher_no)
previous_outstanding_amount = ref_doc.outstanding_amount
outstanding_amount = flt(
outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount")
)
@@ -1899,12 +1954,33 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
outstanding_amount,
)
update_linked_dunnings(ref_doc, previous_outstanding_amount)
ref_doc.set_status(update=True)
ref_doc.notify_update()
def delink_original_entry(pl_entry, partial_cancel=False):
if pl_entry:
if not pl_entry:
return
if pl_entry.doctype == "Advance Payment Ledger Entry":
adv = qb.DocType("Advance Payment Ledger Entry")
(
qb.update(adv)
.set(adv.delinked, 1)
.set(adv.event, "Cancel")
.set(adv.modified, now())
.set(adv.modified_by, frappe.session.user)
.where(adv.voucher_type == pl_entry.voucher_type)
.where(adv.voucher_no == pl_entry.voucher_no)
.where(adv.against_voucher_type == pl_entry.against_voucher_type)
.where(adv.against_voucher_no == pl_entry.against_voucher_no)
.where(adv.event == pl_entry.event)
.run()
)
else:
ple = qb.DocType("Payment Ledger Entry")
query = (
qb.update(ple)
@@ -2347,3 +2423,39 @@ def sync_auto_reconcile_config(auto_reconciliation_job_trigger: int = 15):
"frequency": "Cron",
}
).save()
def get_link_fields_grouped_by_option(doctype):
meta = frappe.get_meta(doctype)
link_fields_map = defaultdict(list)
for df in meta.fields:
if df.fieldtype == "Link" and df.options and not df.ignore_user_permissions:
link_fields_map[df.options].append(df.fieldname)
return link_fields_map
def build_qb_match_conditions(doctype, user=None) -> list:
match_filters = build_match_conditions(doctype, user, False)
link_fields_map = get_link_fields_grouped_by_option(doctype)
criterion = []
apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions")
if match_filters:
_dt = qb.DocType(doctype)
for filter in match_filters:
for link_option, allowed_values in filter.items():
fieldnames = link_fields_map.get(link_option, [])
for fieldname in fieldnames:
field = _dt[fieldname]
cond = field.isin(allowed_values)
if not apply_strict_user_permissions:
cond = (Coalesce(field, "") == "") | cond
criterion.append(cond)
return criterion

View File

@@ -122,6 +122,7 @@ class Asset(AccountsController):
def validate(self):
self.validate_category()
self.validate_precision()
self.validate_linked_purchase_docs()
self.set_purchase_doc_row_item()
self.validate_asset_values()
self.validate_asset_and_reference()
@@ -249,14 +250,11 @@ class Asset(AccountsController):
frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
def prepare_depreciation_data(self):
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
self.opening_accumulated_depreciation
)
if self.calculate_depreciation:
self.value_after_depreciation = 0
self.set_depreciation_rate()
else:
self.finance_books = []
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
self.opening_accumulated_depreciation
)
def validate_item(self):
item = frappe.get_cached_value(
@@ -314,6 +312,9 @@ class Asset(AccountsController):
)
def set_missing_values(self):
if not self.calculate_depreciation:
return
if not self.asset_category:
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
@@ -409,6 +410,21 @@ class Asset(AccountsController):
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
frappe.throw(_("Available-for-use Date should be after purchase date"))
def validate_linked_purchase_docs(self):
for doctype_field, doctype_name in [
("purchase_receipt", "Purchase Receipt"),
("purchase_invoice", "Purchase Invoice"),
]:
linked_doc = getattr(self, doctype_field, None)
if linked_doc:
docstatus = frappe.db.get_value(doctype_name, linked_doc, "docstatus")
if docstatus == 0:
frappe.throw(
_("{0} is still in Draft. Please submit it before saving the Asset.").format(
get_link_to_form(doctype_name, linked_doc)
)
)
def validate_gross_and_purchase_amount(self):
if self.is_existing_asset:
return
@@ -1083,7 +1099,7 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non
def make_journal_entry(asset_name):
asset = frappe.get_doc("Asset", asset_name)
(
_,
fixed_asset_account,
accumulated_depreciation_account,
depreciation_expense_account,
) = get_depreciation_accounts(asset.asset_category, asset.company)
@@ -1097,7 +1113,7 @@ def make_journal_entry(asset_name):
je.voucher_type = "Depreciation Entry"
je.naming_series = depreciation_series
je.company = asset.company
je.remark = f"Depreciation Entry against asset {asset_name}"
je.remark = _("Depreciation Entry against asset {0}").format(asset_name)
je.append(
"accounts",
@@ -1215,6 +1231,10 @@ def update_existing_asset(asset, remaining_qty, new_asset_name):
opening_accumulated_depreciation = flt(
(asset.opening_accumulated_depreciation * remaining_qty) / asset.asset_quantity
)
value_after_depreciation = flt(
(asset.value_after_depreciation * remaining_qty) / asset.asset_quantity,
asset.precision("gross_purchase_amount"),
)
frappe.db.set_value(
"Asset",
@@ -1222,6 +1242,7 @@ def update_existing_asset(asset, remaining_qty, new_asset_name):
{
"opening_accumulated_depreciation": opening_accumulated_depreciation,
"gross_purchase_amount": remaining_gross_purchase_amount,
"value_after_depreciation": value_after_depreciation,
"asset_quantity": remaining_qty,
},
)
@@ -1283,6 +1304,10 @@ def create_new_asset_after_split(asset, split_qty):
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
new_asset.asset_quantity = split_qty
new_asset.split_from = asset.name
new_asset.value_after_depreciation = flt(
(asset.value_after_depreciation * split_qty) / asset.asset_quantity,
asset.precision("gross_purchase_amount"),
)
for row in new_asset.get("finance_books"):
row.value_after_depreciation = flt((row.value_after_depreciation * split_qty) / asset.asset_quantity)
@@ -1291,7 +1316,6 @@ def create_new_asset_after_split(asset, split_qty):
)
new_asset.insert()
add_asset_activity(
new_asset.name,
_("Asset created after being split from Asset {0}").format(get_link_to_form("Asset", asset.name)),

View File

@@ -277,7 +277,9 @@ def _make_journal_entry_for_depreciation(
je.posting_date = depr_schedule.schedule_date
je.company = asset.company
je.finance_book = asset_depr_schedule_doc.finance_book
je.remark = f"Depreciation Entry against {asset.name} worth {depr_schedule.depreciation_amount}"
je.remark = _("Depreciation Entry against {0} worth {1}").format(
asset.name, depr_schedule.depreciation_amount
)
credit_entry = {
"account": credit_account,

View File

@@ -346,6 +346,33 @@ class TestAsset(AssetSetup):
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_asset_status_after_sales_invoice_cancel(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-04-01",
purchase_date="2020-04-01",
expected_value_after_useful_life=0,
total_number_of_depreciations=5,
opening_number_of_booked_depreciations=2,
frequency_of_depreciation=12,
depreciation_start_date="2023-03-31",
opening_accumulated_depreciation=24000,
gross_purchase_amount=60000,
submit=1,
)
si = create_sales_invoice(
item_code="Macbook Pro", asset=asset.name, qty=1, rate=40000, posting_date=getdate("2023-05-23")
)
asset.load_from_db()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
si.cancel()
asset.load_from_db()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_gle_made_by_asset_sale_for_existing_asset(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -445,6 +472,27 @@ class TestAsset(AssetSetup):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
def test_asset_splitting_without_depreciation(self):
asset = create_asset(
calculate_depreciation=0,
asset_quantity=10,
available_for_use_date="2020-01-01",
purchase_date="2020-01-01",
gross_purchase_amount=120000,
submit=1,
)
self.assertEqual(asset.asset_quantity, 10)
self.assertEqual(asset.gross_purchase_amount, 120000)
new_asset = split_asset(asset.name, 2)
asset.load_from_db()
self.assertEqual(asset.asset_quantity, 8)
self.assertEqual(asset.gross_purchase_amount, 96000)
self.assertEqual(new_asset.asset_quantity, 2)
self.assertEqual(new_asset.gross_purchase_amount, 24000)
def test_asset_splitting(self):
asset = create_asset(
calculate_depreciation=1,
@@ -1492,7 +1540,6 @@ class TestDepreciationBasics(AssetSetup):
)
self.assertSequenceEqual(gle, expected_gle)
self.assertEqual(asset.get("value_after_depreciation"), 0)
def test_expected_value_change(self):
"""

View File

@@ -98,20 +98,41 @@ class AssetDepreciationSchedule(Document):
)
def on_submit(self):
self.validate_asset()
self.db_set("status", "Active")
def before_cancel(self):
def validate_asset(self):
asset = frappe.get_doc("Asset", self.asset)
if not asset.calculate_depreciation:
frappe.throw(
_("Asset {0} is not set to calculate depreciation.").format(
get_link_to_form("Asset", self.asset)
)
)
if asset.docstatus != 1:
frappe.throw(
_("Asset {0} is not submitted. Please submit the asset before proceeding.").format(
get_link_to_form("Asset", self.asset)
)
)
def on_cancel(self):
self.db_set("status", "Cancelled")
if not self.flags.should_not_cancel_depreciation_entries:
self.cancel_depreciation_entries()
def cancel_depreciation_entries(self):
for d in self.get("depreciation_schedule"):
if d.journal_entry:
je_status = frappe.db.get_value("Journal Entry", d.journal_entry, "docstatus")
if je_status == 0:
frappe.throw(
_(
"Cannot cancel Asset Depreciation Schedule {0} as it has a draft journal entry {1}."
).format(self.name, d.journal_entry)
)
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
def on_cancel(self):
self.db_set("status", "Cancelled")
def update_shift_depr_schedule(self):
if not self.shift_based or self.docstatus != 0:
return

View File

@@ -3,13 +3,13 @@ frappe.listview_settings["Asset Maintenance Log"] = {
has_indicator_for_draft: 1,
get_indicator: function (doc) {
if (doc.maintenance_status == "Planned") {
return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status];
return [__(doc.maintenance_status), "orange", "maintenance_status,=," + doc.maintenance_status];
} else if (doc.maintenance_status == "Completed") {
return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status];
return [__(doc.maintenance_status), "green", "maintenance_status,=," + doc.maintenance_status];
} else if (doc.maintenance_status == "Cancelled") {
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
return [__(doc.maintenance_status), "red", "maintenance_status,=," + doc.maintenance_status];
} else if (doc.maintenance_status == "Overdue") {
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
return [__(doc.maintenance_status), "red", "maintenance_status,=," + doc.maintenance_status];
}
},
};

View File

@@ -74,6 +74,7 @@
"fieldname": "completion_date",
"fieldtype": "Datetime",
"label": "Completion Date",
"mandatory_depends_on": "eval:doc.repair_status==\"Completed\"",
"no_copy": 1
},
{
@@ -249,7 +250,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-06-29 22:30:00.589597",
"modified": "2025-07-29 15:14:34.044564",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair",
@@ -287,10 +288,11 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "asset_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -70,7 +70,7 @@ class AssetRepair(AccountsController):
)
def validate_dates(self):
if self.completion_date and (self.failure_date > self.completion_date):
if self.completion_date and (getdate(self.failure_date) > getdate(self.completion_date)):
frappe.throw(
_("Completion Date can not be before Failure Date. Please adjust the dates accordingly.")
)
@@ -303,7 +303,7 @@ class AssetRepair(AccountsController):
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"posting_date": self.completion_date,
"against_voucher_type": "Purchase Invoice",
"against_voucher": self.purchase_invoice,
"company": self.company,
@@ -322,7 +322,7 @@ class AssetRepair(AccountsController):
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"posting_date": self.completion_date,
"company": self.company,
},
item=self,
@@ -356,7 +356,7 @@ class AssetRepair(AccountsController):
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"posting_date": self.completion_date,
"company": self.company,
},
item=self,
@@ -373,7 +373,7 @@ class AssetRepair(AccountsController):
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"posting_date": self.completion_date,
"against_voucher_type": "Stock Entry",
"against_voucher": stock_entry.name,
"company": self.company,

View File

@@ -4,7 +4,7 @@
import unittest
import frappe
from frappe.utils import add_months, flt, get_first_day, nowdate, nowtime, today
from frappe.utils import add_days, add_months, flt, get_first_day, nowdate, nowtime, today
from erpnext.assets.doctype.asset.asset import (
get_asset_account,
@@ -359,6 +359,7 @@ def create_asset_repair(**args):
if args.submit:
asset_repair.repair_status = "Completed"
asset_repair.completion_date = add_days(args.failure_date, 1)
asset_repair.cost_center = frappe.db.get_value("Company", asset.company, "cost_center")
if args.stock_consumption:

View File

@@ -48,7 +48,6 @@ class AssetValueAdjustment(Document):
def on_submit(self):
self.make_depreciation_entry()
self.set_value_after_depreciation()
self.update_asset(self.new_asset_value)
add_asset_activity(
self.asset,
@@ -80,9 +79,6 @@ class AssetValueAdjustment(Document):
def set_difference_amount(self):
self.difference_amount = flt(self.new_asset_value - self.current_asset_value)
def set_value_after_depreciation(self):
frappe.db.set_value("Asset", self.asset, "value_after_depreciation", self.new_asset_value)
def set_current_asset_value(self):
if not self.current_asset_value and self.asset:
self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book)
@@ -164,12 +160,8 @@ class AssetValueAdjustment(Document):
self.db_set("journal_entry", je.name)
def update_asset(self, asset_value=None):
asset = frappe.get_doc("Asset", self.asset)
if not asset.calculate_depreciation:
asset.value_after_depreciation = asset_value
asset.save()
return
difference_amount = self.difference_amount if self.docstatus == 1 else -1 * self.difference_amount
asset = self.update_asset_value_after_depreciation(difference_amount)
asset.flags.decrease_in_asset_value_due_to_value_adjustment = True
@@ -188,19 +180,6 @@ class AssetValueAdjustment(Document):
get_link_to_form(self.get("doctype"), self.get("name")),
)
difference_amount = self.difference_amount if self.docstatus == 1 else -1 * self.difference_amount
if asset.calculate_depreciation:
for row in asset.finance_books:
if cstr(row.finance_book) == cstr(self.finance_book):
salvage_value_adjustment = (
self.get_adjusted_salvage_value_amount(row, difference_amount) or 0
)
row.expected_value_after_useful_life += salvage_value_adjustment
row.value_after_depreciation += flt(difference_amount)
row.db_update()
asset.db_update()
make_new_active_asset_depr_schedules_and_cancel_current_ones(
asset,
notes,
@@ -212,6 +191,23 @@ class AssetValueAdjustment(Document):
asset.save()
asset.set_status()
def update_asset_value_after_depreciation(self, difference_amount):
asset = frappe.get_doc("Asset", self.asset)
if asset.calculate_depreciation:
for row in asset.finance_books:
if cstr(row.finance_book) == cstr(self.finance_book):
salvage_value_adjustment = (
self.get_adjusted_salvage_value_amount(row, difference_amount) or 0
)
row.expected_value_after_useful_life += salvage_value_adjustment
row.value_after_depreciation = row.value_after_depreciation + flt(difference_amount)
row.db_update()
asset.value_after_depreciation += flt(difference_amount)
asset.db_update()
return asset
def get_adjusted_salvage_value_amount(self, row, difference_amount):
if row.expected_value_after_useful_life:
salvage_value_adjustment = (difference_amount * row.salvage_value_percentage) / 100

View File

@@ -282,13 +282,13 @@ class TestAssetValueAdjustment(unittest.TestCase):
adj_doc = make_asset_value_adjustment(
asset=asset_doc.name,
current_asset_value=54000,
current_asset_value=120000.0,
new_asset_value=50000.0,
date="2023-08-21",
)
adj_doc.submit()
difference_amount = adj_doc.new_asset_value - adj_doc.current_asset_value
self.assertEqual(difference_amount, -4000)
self.assertEqual(difference_amount, -70000)
asset_doc.load_from_db()
self.assertEqual(asset_doc.value_after_depreciation, 50000.0)

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