Compare commits

...

124 Commits

Author SHA1 Message Date
mergify[bot]
bd7e5b3e05 refactor: use consistent report column names (backport #54451) (backport #54478) (#54506)
* refactor: use consistent report column names

(cherry picked from commit 7630c01e40)

# Conflicts:
#	erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
(cherry picked from commit ab188c4404)

* refactor: better label for entity type

(cherry picked from commit 8e12bda108)
(cherry picked from commit e1ff203f95)

* fix: add party_type for dynamic link and add it to grouping key

(cherry picked from commit a3ad1fb163)
(cherry picked from commit 6eb2868a15)

* fix: use key consistently

(cherry picked from commit 8f9a5e6c0c)
(cherry picked from commit 32d46b3e88)

* chore: resolve conflicts

(cherry picked from commit 34e94d6e7a)

* chore: translate values correctly

(cherry picked from commit 83fd655042)

---------

Co-authored-by: Smit Vora <smitvora203@gmail.com>
2026-04-24 20:01:36 +05:30
Frappe PR Bot
adf92a8292 chore(release): Bumped to Version 14.92.14
## [14.92.14](https://github.com/frappe/erpnext/compare/v14.92.13...v14.92.14) (2026-04-10)

### Bug Fixes

* added exception handling on service level agreement apply hook ([#50096](https://github.com/frappe/erpnext/issues/50096)) ([#54192](https://github.com/frappe/erpnext/issues/54192)) ([12b2788](12b27883f6))
2026-04-10 11:53:37 +00:00
diptanilsaha
df6dc540f8 Merge pull request #54202 from frappe/mergify/bp/version-14/pr-54192
fix: added exception handling on service level agreement apply hook (#50096) (backport #54192)
2026-04-10 17:22:07 +05:30
diptanilsaha
12b27883f6 fix: added exception handling on service level agreement apply hook (#50096) (#54192)
Co-authored-by: Ankush Menat <ankush@frappe.io>
(cherry picked from commit 21311dab86)
2026-04-10 11:33:06 +00:00
Frappe PR Bot
5bbfb79cdc chore(release): Bumped to Version 14.92.13
## [14.92.13](https://github.com/frappe/erpnext/compare/v14.92.12...v14.92.13) (2026-03-21)

### Bug Fixes

* allow zero valuation rate ([7a98e13](7a98e13d7d))
2026-03-21 08:34:35 +00:00
rohitwaghchaure
266ac2d5be Merge pull request #53637 from frappe/mergify/bp/version-14/pr-53635
fix: allow zero valuation rate (backport #53635)
2026-03-21 14:03:04 +05:30
Rohit Waghchaure
7a98e13d7d fix: allow zero valuation rate
(cherry picked from commit a6eadb18c9)
2026-03-19 08:32:50 +00:00
rohitwaghchaure
aeee42e857 Merge pull request #52349 from frappe/version-14-hotfix
chore: release v14
2026-02-03 22:48:16 +05:30
Mihir Kandoi
e1674d2017 Merge pull request #51658 from mihir-kandoi/eol-msg 2026-01-30 17:09:28 +05:30
Mihir Kandoi
65d7c6b882 chore: rename filename 2026-01-30 17:08:48 +05:30
Frappe PR Bot
966f262e38 chore(release): Bumped to Version 14.92.12
## [14.92.12](https://github.com/frappe/erpnext/compare/v14.92.11...v14.92.12) (2026-01-27)

### Bug Fixes

* **stock:** remove limit filter while fetching batch and bin ([07ac93d](07ac93d06a))
2026-01-27 14:42:02 +00:00
ruthra kumar
44f81092b6 Merge pull request #52105 from frappe/version-14-hotfix
chore: release v14
2026-01-27 20:09:29 +05:30
rohitwaghchaure
6f1616bc95 Merge pull request #51939 from aerele/fix/pick-list-qty-validation
fix(stock): remove limit filter while fetching batch and bin
2026-01-23 19:36:54 +05:30
Sudharsanan11
07ac93d06a fix(stock): remove limit filter while fetching batch and bin 2026-01-20 23:12:13 +05:30
Frappe PR Bot
34499d7f77 chore(release): Bumped to Version 14.92.11
## [14.92.11](https://github.com/frappe/erpnext/compare/v14.92.10...v14.92.11) (2026-01-20)

### Bug Fixes

* Show non-SLE vouchers with GL entries in Stock vs Account Value Comparison report ([6e03d45](6e03d45034))
* **transaction.js:** use flt instead of cint for plc_conversion_rate ([d09de53](d09de53d1d))
2026-01-20 16:37:18 +00:00
ruthra kumar
2967de1999 Merge pull request #51910 from frappe/version-14-hotfix
chore: release v14
2026-01-20 22:05:36 +05:30
ruthra kumar
824beaa893 Merge pull request #51837 from frappe/mergify/bp/version-14-hotfix/pr-51787
fix: recalculate taxes when item tax template changes after discount (backport #51787)
2026-01-19 14:37:18 +05:30
ljain112
b4dc1be5ed chore: resolve conflicts 2026-01-19 13:45:11 +05:30
Lakshit Jain
cf49c32b94 Merge pull request #51787 from ljain112/fix-taxes-disc
fix: recalculate taxes when item tax template changes after discount
(cherry picked from commit f00aeec9b4)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
#	erpnext/controllers/taxes_and_totals.py
2026-01-19 07:01:36 +00:00
rohitwaghchaure
d54f45469a Merge pull request #51770 from frappe/mergify/bp/version-14-hotfix/pr-51768
fix: Show non-SLE vouchers with GL entries in Stock vs Account Value … (backport #51768)
2026-01-15 20:59:58 +05:30
rohitwaghchaure
ad5181c52f chore: fix conflicts 2026-01-15 19:28:44 +05:30
Rohit Waghchaure
6e03d45034 fix: Show non-SLE vouchers with GL entries in Stock vs Account Value Comparison report
(cherry picked from commit 1db9ce205f)

# Conflicts:
#	erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
2026-01-15 12:20:32 +00:00
Diptanil Saha
7a18f86dd5 Merge pull request #51746 from frappe/mergify/bp/version-14-hotfix/pr-51730
fix(transaction.js): use flt instead of cint for plc_conversion_rate (backport #51730)
2026-01-14 15:56:06 +05:30
Diptanil Saha
39e1bddce6 chore: resolve conflict 2026-01-14 15:53:18 +05:30
diptanilsaha
d09de53d1d fix(transaction.js): use flt instead of cint for plc_conversion_rate
(cherry picked from commit 8b445e04e5)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
2026-01-14 10:22:04 +00:00
Frappe PR Bot
8035940263 chore(release): Bumped to Version 14.92.10
## [14.92.10](https://github.com/frappe/erpnext/compare/v14.92.9...v14.92.10) (2026-01-13)

### Bug Fixes

* don't duplicate default income account to Item ([#50413](https://github.com/frappe/erpnext/issues/50413)) ([55d2a54](55d2a54785)), closes [#48231](https://github.com/frappe/erpnext/issues/48231)
* negative stock issue for higher precision ([21d1385](21d13859a0))
2026-01-13 14:44:57 +00:00
ruthra kumar
c69f3d330a Merge pull request #51712 from frappe/version-14-hotfix
chore: release v14
2026-01-13 20:13:30 +05:30
Mihir Kandoi
90d88b1c26 chore: fix grammar 2026-01-13 11:00:42 +05:30
Mihir Kandoi
22eec09175 chore: update links 2026-01-13 10:52:51 +05:30
ruthra kumar
dbfd4ea4d3 Merge pull request #51331 from frappe/mergify/bp/version-14-hotfix/pr-50413
fix: don't duplicate default income account to Item (backport #50413)
2026-01-12 20:34:03 +05:30
barredterra
8d28dcb18b chore: resolve conflicts 2026-01-11 17:59:27 +01:00
Mihir Kandoi
3f186b24f3 chore: EOL announcement for v14 2026-01-10 22:44:44 +05:30
rohitwaghchaure
4b0ddbf73a Merge pull request #51591 from frappe/mergify/bp/version-14-hotfix/pr-51588
fix: negative stock issue for higher precision (backport #51586) (backport #51588)
2026-01-08 16:11:20 +05:30
rohitwaghchaure
cddf1e1ee5 chore: fix conflicts
Refactor delivery note tests to improve clarity and organization. Remove redundant test cases and ensure proper valuation checks for batch and serial items.
2026-01-08 15:18:53 +05:30
rohitwaghchaure
22e5aba02b chore: fix conflicts
Refactor test cases for delivery notes to handle negative stock and higher precision.

(cherry picked from commit 5193dbba9b)
2026-01-08 09:35:44 +00:00
Rohit Waghchaure
21d13859a0 fix: negative stock issue for higher precision
(cherry picked from commit 87be020c78)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/test_delivery_note.py
(cherry picked from commit 1bbeecff12)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/test_delivery_note.py
2026-01-08 09:35:44 +00:00
Frappe PR Bot
18fbe69892 chore(release): Bumped to Version 14.92.9
## [14.92.9](https://github.com/frappe/erpnext/compare/v14.92.8...v14.92.9) (2026-01-07)

### Bug Fixes

* update filters on period closing voucher ([6499a1d](6499a1dae0))
2026-01-07 04:58:04 +00:00
ruthra kumar
f2043baf36 Merge pull request #51539 from frappe/version-14-hotfix
chore: release v14
2026-01-07 10:26:40 +05:30
ruthra kumar
6267b9aea5 Merge pull request #51486 from frappe/mergify/bp/version-14-hotfix/pr-51467
fix: update filters on period closing voucher (backport #51467)
2026-01-05 10:53:20 +05:30
SowmyaArunachalam
6499a1dae0 fix: update filters on period closing voucher
(cherry picked from commit 7ab1e1f677)
2026-01-05 05:20:31 +00:00
Sagar Vora
478992812f Merge pull request #51410 from frappe/revert-51302-pin-urllib3 2025-12-31 13:22:41 +05:30
Sagar Vora
634dd11d72 Revert "fix(deps): pin urllib3" 2025-12-31 13:18:09 +05:30
Frappe PR Bot
29134db5b4 chore(release): Bumped to Version 14.92.8
## [14.92.8](https://github.com/frappe/erpnext/compare/v14.92.7...v14.92.8) (2025-12-30)

### Bug Fixes

* **stock:** update last incoming rate in serial no doctype ([5e12937](5e129374b9))
2025-12-30 13:33:09 +00:00
ruthra kumar
a2597c9608 Merge pull request #51390 from frappe/version-14-hotfix
chore: release v14
2025-12-30 19:01:44 +05:30
rohitwaghchaure
0e6586734a Merge pull request #51365 from aerele/fix/serial-item-incoming-rate
fix(stock): update last incoming rate in serial no doctype
2025-12-29 23:31:07 +05:30
Sudharsanan11
5e129374b9 fix(stock): update last incoming rate in serial no doctype 2025-12-29 16:54:09 +05:30
Raffael Meyer
55d2a54785 fix: don't duplicate default income account to Item (#50413)
* fix: don't duplicate default income account to Item

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

Resolves #48231

* refactor: move db call out of loop

* docs: add docstring

(cherry picked from commit b6cb9d4799)

# Conflicts:
#	erpnext/controllers/selling_controller.py
2025-12-25 09:23:09 +00:00
Frappe PR Bot
b3bf456ea2 chore(release): Bumped to Version 14.92.7
## [14.92.7](https://github.com/frappe/erpnext/compare/v14.92.6...v14.92.7) (2025-12-24)

### Bug Fixes

* available qty not fetched on selection of batch ([9841438](98414385e5))
* **deps:** pin urllib3<2 ([1dee100](1dee10077c))
* disable uv ([d713f39](d713f39fce))
* **patch:** fallback for frankfurter settings v14 patch ([5c9e0bb](5c9e0bba2f))

### Performance Improvements

* optimize company monthly sales query using date range ([#48942](https://github.com/frappe/erpnext/issues/48942)) ([e9c1d0a](e9c1d0ad52))
2025-12-24 08:23:59 +00:00
rohitwaghchaure
932dc66871 Merge pull request #51279 from frappe/version-14-hotfix
chore: release v14
2025-12-24 13:52:30 +05:30
Akhil Narang
3a6d056fd3 Merge pull request #51302 from akhilnarang/pin-urllib3
fix(deps): pin urllib3
2025-12-24 12:52:26 +05:30
Akhil Narang
d713f39fce fix: disable uv
Seems to cause an issue with dependency resolution when installation dev dependencies

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-12-24 12:29:21 +05:30
Akhil Narang
1dee10077c fix(deps): pin urllib3<2
v2 has breaking changes, and some other ERPNext dependencies pull in newer requests, which has urllib3<3 mentioned.

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2025-12-24 11:26:39 +05:30
ruthra kumar
c86cf5b9c6 Merge pull request #51291 from frappe/mergify/bp/version-14-hotfix/pr-51285
fix(patch): handle currency exchange settings frankfurter api update for older versions (backport #51285)
2025-12-23 20:46:16 +05:30
diptanilsaha
5c9e0bba2f fix(patch): fallback for frankfurter settings v14 patch
(cherry picked from commit 50bb1ce31d)
2025-12-23 12:11:25 +00:00
rohitwaghchaure
793688710a Merge pull request #51261 from rohitwaghchaure/fixed-support-53592
fix: available qty not fetched on selection of batch
2025-12-22 16:52:30 +05:30
Rohit Waghchaure
98414385e5 fix: available qty not fetched on selection of batch 2025-12-22 16:47:38 +05:30
Diptanil Saha
33f7b742ec Merge pull request #51147 from frappe/mergify/bp/version-14-hotfix/pr-48942 2025-12-17 11:57:24 +05:30
Yash Chaubey
e9c1d0ad52 perf: optimize company monthly sales query using date range (#48942)
* perf: optimize company monthly sales query using date range instead of DATE_FORMAT

* perf: optimize company monthly sales query using date range

(cherry picked from commit 4ede97ae2b)
2025-12-17 06:07:27 +00:00
Frappe PR Bot
e733b41ab3 chore(release): Bumped to Version 14.92.6
## [14.92.6](https://github.com/frappe/erpnext/compare/v14.92.5...v14.92.6) (2025-12-16)

### Bug Fixes

* **asset:** calculate depreciation amount for non prorata based schedules ([8de0d60](8de0d60581))
* **asset:** prorata daily depr amount calculation ([bc58fd1](bc58fd1fa4))
* **currency exchange settings:** added backward compatibility for frankfurter api ([a15b010](a15b010868))
* **share balance:** use currency field instead of int for rate and amount ([a299392](a299392128))
* Short circuit guest perm checks ([6f6cbb7](6f6cbb717e))
* **transaction-deletion:** Add virtual doctypes to the list of ignored doctypes (backport [#51063](https://github.com/frappe/erpnext/issues/51063)) ([#51086](https://github.com/frappe/erpnext/issues/51086)) ([67c0d08](67c0d08569))
* update expected schedules on test case test_schedule_for_straight_line_method_for_existing_asset ([6ea7393](6ea7393f7d))
2025-12-16 15:12:25 +00:00
ruthra kumar
39fd4a827c Merge pull request #51125 from frappe/version-14-hotfix
chore: release v14
2025-12-16 20:40:57 +05:30
mergify[bot]
67c0d08569 fix(transaction-deletion): Add virtual doctypes to the list of ignored doctypes (backport #51063) (#51086)
* fix: Add virtual doctypes to the list of ignored doctypes in transaction deletion

(cherry picked from commit c7a7cb2b90)

* refactor: switch to `or_filters` so the query hits the DB only once

(cherry picked from commit 45a7195abe)

* refactor: remove redundant assignment of doctypes_to_be_ignored_list

(cherry picked from commit 0f7d89f4d1)

---------

Co-authored-by: KerollesFathy <kerollesfhabib@gmail.com>
2025-12-14 14:31:02 +05:30
Ankush Menat
6f6cbb717e fix: Short circuit guest perm checks 2025-12-14 12:12:35 +05:30
Diptanil Saha
387657fa92 Merge pull request #51044 from frappe/mergify/bp/version-14-hotfix/pr-51037
fix(currency exchange settings): added backward compatibility for frankfurter api (backport #51037)
2025-12-11 16:27:54 +05:30
Diptanil Saha
ef03d90a08 chore: resolve conflict 2025-12-11 15:58:30 +05:30
diptanilsaha
a15b010868 fix(currency exchange settings): added backward compatibility for frankfurter api
(cherry picked from commit 5c2bb66028)

# Conflicts:
#	erpnext/patches.txt
2025-12-11 10:26:17 +00:00
Khushi Rawat
9d00afe1b1 Merge pull request #50964 from aerele/daily-prorata-depr-amount-calculation
fix(asset): prorata daily depr amount calculation
2025-12-10 14:22:17 +05:30
Navin-S-R
6ea7393f7d fix: update expected schedules on test case test_schedule_for_straight_line_method_for_existing_asset 2025-12-10 13:35:57 +05:30
Navin-S-R
8de0d60581 fix(asset): calculate depreciation amount for non prorata based schedules 2025-12-10 12:38:27 +05:30
Diptanil Saha
a67092894f Merge pull request #51002 from frappe/mergify/bp/version-14-hotfix/pr-51001
fix(share balance): use currency field instead of int for rate and amount (backport #51001)
2025-12-10 10:38:26 +05:30
Diptanil Saha
77b9044d8d chore: resolve conflict 2025-12-10 09:56:15 +05:30
Diptanil Saha
ba9f571886 chore: resolve conflict 2025-12-10 09:52:37 +05:30
diptanilsaha
a299392128 fix(share balance): use currency field instead of int for rate and amount
(cherry picked from commit 2fe5fad884)

# Conflicts:
#	erpnext/accounts/doctype/share_balance/share_balance.json
#	erpnext/accounts/doctype/share_balance/share_balance.py
2025-12-10 04:21:05 +00:00
Navin-S-R
bc58fd1fa4 fix(asset): prorata daily depr amount calculation 2025-12-08 15:38:48 +05:30
Frappe PR Bot
1920259180 chore(release): Bumped to Version 14.92.5
## [14.92.5](https://github.com/frappe/erpnext/compare/v14.92.4...v14.92.5) (2025-12-03)

### Bug Fixes

* **Job Card:** avoid Type Error when completed_qty is None (backport [#50447](https://github.com/frappe/erpnext/issues/50447)) ([#50760](https://github.com/frappe/erpnext/issues/50760)) ([5e05873](5e05873226))
* update uom when item changes ([a3b120b](a3b120b3b2))
2025-12-03 05:43:40 +00:00
Diptanil Saha
ea1ca7111d Merge pull request #50867 from frappe/version-14-hotfix 2025-12-03 11:12:16 +05:30
rohitwaghchaure
10f1c9c3ac Merge pull request #50763 from aerele/support-52987
fix: update uom when item changes
2025-11-26 23:42:32 +05:30
Pugazhendhi Velu
a3b120b3b2 fix: update uom when item changes 2025-11-26 16:07:27 +00:00
mergify[bot]
5e05873226 fix(Job Card): avoid Type Error when completed_qty is None (backport #50447) (#50760)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Job Card): avoid Type Error when completed_qty is None (#50447)
2025-11-26 14:55:53 +01:00
ruthra kumar
14a7ab71b4 Merge pull request #50747 from frappe/version-14-hotfix
chore: release v14
2025-11-26 10:17:16 +05:30
mergify[bot]
db828c08b6 chore: switched frankfurter domain from frankfurter.app to frankfurter.dev (backport #50734) (#50739)
Co-authored-by: diptanilsaha <diptanil@frappe.io>
2025-11-25 15:49:37 +05:30
Frappe PR Bot
993890116b chore(release): Bumped to Version 14.92.4
## [14.92.4](https://github.com/frappe/erpnext/compare/v14.92.3...v14.92.4) (2025-11-18)

### Bug Fixes

* current qty in stock reco ([5979683](5979683e8e))
* **process statement of accounts:** use date instead of formatted date ([1ef9471](1ef9471c79))
2025-11-18 13:09:28 +00:00
Diptanil Saha
cd437b5a26 Merge pull request #50594 from frappe/version-14-hotfix 2025-11-18 18:37:55 +05:30
Diptanil Saha
e6f505ef81 Merge pull request #50523 from frappe/mergify/bp/version-14-hotfix/pr-49086
fix(process statement of accounts): use date instead of formatted date (backport #49086)
2025-11-13 22:16:06 +05:30
ravibharathi656
1ef9471c79 fix(process statement of accounts): use date instead of formatted date
(cherry picked from commit aa3f50ab77)
2025-11-13 16:24:05 +00:00
rohitwaghchaure
304aad6745 Merge pull request #50490 from frappe/mergify/bp/version-14-hotfix/pr-50487
fix: current qty in stock reconciliation  (backport #50487)
2025-11-12 22:49:37 +05:30
rohitwaghchaure
c80fed8b2f chore: fix conflicts 2025-11-12 13:57:00 +05:30
Rohit Waghchaure
5979683e8e fix: current qty in stock reco
(cherry picked from commit 58315bc963)

# Conflicts:
#	erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
2025-11-12 08:25:59 +00:00
Frappe PR Bot
457a81123b chore(release): Bumped to Version 14.92.3
## [14.92.3](https://github.com/frappe/erpnext/compare/v14.92.2...v14.92.3) (2025-11-11)

### Bug Fixes

* set company before creating asset movement to avoid permission error ([8dc80c9](8dc80c948e))
2025-11-11 14:42:37 +00:00
Diptanil Saha
d7966183be Merge pull request #50474 from frappe/version-14-hotfix 2025-11-11 20:11:02 +05:30
Khushi Rawat
367bd58e76 Merge pull request #50378 from frappe/mergify/bp/version-14-hotfix/pr-50367
fix: set company before creating asset movement to avoid permission error (backport #50367)
2025-11-06 10:03:10 +05:30
rehansari26
8dc80c948e fix: set company before creating asset movement to avoid permission error
(cherry picked from commit 8c49c9e500)
2025-11-05 21:50:18 +00:00
Diptanil Saha
0e3cc120dc Merge pull request #50334 from frappe/version-14-hotfix
chore: release v14
2025-11-04 18:00:16 +05:30
ruthra kumar
b54c5f61d6 Merge pull request #50308 from frappe/mergify/bp/version-14-hotfix/pr-50297
chore:there is no company field in bank (backport #50297)
2025-11-03 14:04:23 +05:30
Nihal Roshan
c51a9025b3 refactor: remove company filter
'Bank' doctype has no 'company' field.

(cherry picked from commit f1682ea90e)
2025-11-03 08:31:53 +00:00
Frappe PR Bot
616448f9c9 chore(release): Bumped to Version 14.92.2
## [14.92.2](https://github.com/frappe/erpnext/compare/v14.92.1...v14.92.2) (2025-10-21)

### Bug Fixes

* do reposting of first transfer entry based on item-wh combination ([85c509e](85c509ea69))
* handle flt conversion for prev_ordered_qty ([9460568](94605687c7))
2025-10-21 12:45:22 +00:00
Diptanil Saha
70a8c64ea4 Merge pull request #50181 from frappe/version-14-hotfix
chore: release v14
2025-10-21 18:13:40 +05:30
mergify[bot]
00a75d4984 refactor: simplify expression (backport #50168) (#50169)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-10-19 23:59:31 +02:00
rohitwaghchaure
8b826c2369 Merge pull request #50121 from frappe/mergify/bp/version-14-hotfix/pr-50061
fix: do reposting of first transfer entry based on item-wh combination (backport #50061)
2025-10-16 15:02:52 +05:30
rohitwaghchaure
210786119d chore: fix conflicts 2025-10-16 00:17:50 +05:30
Rohit Waghchaure
85c509ea69 fix: do reposting of first transfer entry based on item-wh combination
(cherry picked from commit 2f25b445ab)

# Conflicts:
#	erpnext/stock/stock_ledger.py
2025-10-15 18:44:46 +00:00
Mihir Kandoi
6e35b6b6b3 Merge pull request #50105 from frappe/mergify/bp/version-14-hotfix/pr-50078
fix: handle flt conversion for prev_ordered_qty (backport #50078)
2025-10-15 00:15:14 +05:30
Kavin
94605687c7 fix: handle flt conversion for prev_ordered_qty
(cherry picked from commit 77c35ef47f)
2025-10-14 18:10:27 +00:00
Frappe PR Bot
e27518b2d4 chore(release): Bumped to Version 14.92.1
## [14.92.1](https://github.com/frappe/erpnext/compare/v14.92.0...v14.92.1) (2025-10-14)

### Bug Fixes

* sanitize projects field in tasks webform ([#50089](https://github.com/frappe/erpnext/issues/50089)) ([aadc83d](aadc83d0d9))
2025-10-14 14:18:29 +00:00
Diptanil Saha
cf71178af6 Merge pull request #50099 from frappe/version-14-hotfix
chore: release v14
2025-10-14 19:46:52 +05:30
ruthra kumar
002575244f Merge pull request #50097 from frappe/mergify/bp/version-14-hotfix/pr-50089
fix: sanitize projects field in tasks webform (backport #50089)
2025-10-14 19:22:52 +05:30
Akhil Narang
aadc83d0d9 fix: sanitize projects field in tasks webform (#50089)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
(cherry picked from commit f8b50d3ffa)
2025-10-14 13:08:25 +00:00
Frappe PR Bot
66f1e2a076 chore(release): Bumped to Version 14.92.0
# [14.92.0](https://github.com/frappe/erpnext/compare/v14.91.3...v14.92.0) (2025-10-07)

### Bug Fixes

* **Common Code:** fetch canonical URI from Code List (backport [#49882](https://github.com/frappe/erpnext/issues/49882)) ([#49883](https://github.com/frappe/erpnext/issues/49883)) ([9b5183e](9b5183e0e5))
* do not fetch disabled item tax template ([dcae024](dcae024fd2))
* linter; dont change doc after DB update (backport [#49907](https://github.com/frappe/erpnext/issues/49907)) ([#49909](https://github.com/frappe/erpnext/issues/49909)) ([30a6fe5](30a6fe55ca))
* resolved conflict ([2c383a6](2c383a69f9))
* Set paid amount automatically only if return entry validated and has negative grand total ([#49829](https://github.com/frappe/erpnext/issues/49829)) ([b7d76d8](b7d76d8fad))

### Features

* dynamic due date in payment terms when fetched from order (backport [#48864](https://github.com/frappe/erpnext/issues/48864)) ([#49937](https://github.com/frappe/erpnext/issues/49937)) ([d82106c](d82106cb76))
2025-10-07 13:18:52 +00:00
ruthra kumar
1d5935a10f chore: release v14 (#49943)
* refactor(Supplier): custom buttons call make methods (backport #49840) (#49841)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>

* fix(Common Code): fetch canonical URI from Code List (backport #49882) (#49883)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Common Code): fetch canonical URI from Code List (#49882)

* fix: Set paid amount automatically only if return entry validated and has negative grand total (#49829)

(cherry picked from commit dcbcc596f2)

# Conflicts:
#	erpnext/public/js/controllers/taxes_and_totals.js

* fix: resolved conflict

* fix: linter; dont change doc after DB update (backport #49907) (#49909)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
fix: linter; dont change doc after DB update (#49907)

* fix: do not fetch disabled item tax template

(cherry picked from commit b10cf4a928)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
#	erpnext/stock/get_item_details.py

* chore: fix conflicts

* chore: fix conflicts

* chore: fix linters issue

* feat: dynamic due date in payment terms when fetched from order (backport #48864) (#49937)

* feat: dynamic due date in payment terms when fetched from order (#48864)

* fix: dynamic due date when payment terms are fetched from order

* fix(test): use change_settings decorator for settings enable and disable

* fix(test): compare schedule for due_date dynamically

* fix: save conditions for due date at invoice level

* fix: make fields read only and on change of date unset the date condition fields

* fix: remove fetch_form

* fix: correct field assingment

* fix: revert unwanted changes

* refactor: streamline payment term field assignments and enhance discount date handling

* refactor: remove payment_term from fields_to_copy and optimize currency handling in transaction callback

* refactor: ensure default values for payment schedule and discount validity fields

(cherry picked from commit 3c70cbbaf8)

# Conflicts:
#	erpnext/accounts/doctype/payment_schedule/payment_schedule.json
#	erpnext/accounts/doctype/payment_schedule/payment_schedule.py
#	erpnext/public/js/controllers/transaction.js
#	erpnext/selling/doctype/sales_order/test_sales_order.py
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: resolve conflicts

---------

Co-authored-by: Lakshit Jain <ljain112@gmail.com>

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
Co-authored-by: ljain112 <ljain112@gmail.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-10-07 18:47:23 +05:30
mergify[bot]
d82106cb76 feat: dynamic due date in payment terms when fetched from order (backport #48864) (#49937)
* feat: dynamic due date in payment terms when fetched from order (#48864)

* fix: dynamic due date when payment terms are fetched from order

* fix(test): use change_settings decorator for settings enable and disable

* fix(test): compare schedule for due_date dynamically

* fix: save conditions for due date at invoice level

* fix: make fields read only and on change of date unset the date condition fields

* fix: remove fetch_form

* fix: correct field assingment

* fix: revert unwanted changes

* refactor: streamline payment term field assignments and enhance discount date handling

* refactor: remove payment_term from fields_to_copy and optimize currency handling in transaction callback

* refactor: ensure default values for payment schedule and discount validity fields

(cherry picked from commit 3c70cbbaf8)

# Conflicts:
#	erpnext/accounts/doctype/payment_schedule/payment_schedule.json
#	erpnext/accounts/doctype/payment_schedule/payment_schedule.py
#	erpnext/public/js/controllers/transaction.js
#	erpnext/selling/doctype/sales_order/test_sales_order.py
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: resolve conflicts

---------

Co-authored-by: Lakshit Jain <ljain112@gmail.com>
2025-10-07 14:10:46 +05:30
rohitwaghchaure
03aeac6c9b Merge pull request #49931 from frappe/mergify/bp/version-14-hotfix/pr-49702
fix: do not fetch disabled item tax template (backport #49702)
2025-10-07 11:54:19 +05:30
rohitwaghchaure
014f5bff5c chore: fix linters issue 2025-10-07 11:24:49 +05:30
rohitwaghchaure
819667ab24 chore: fix conflicts 2025-10-07 11:00:45 +05:30
rohitwaghchaure
aa20e224fb chore: fix conflicts 2025-10-07 11:00:02 +05:30
ljain112
dcae024fd2 fix: do not fetch disabled item tax template
(cherry picked from commit b10cf4a928)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
#	erpnext/stock/get_item_details.py
2025-10-07 05:26:09 +00:00
mergify[bot]
30a6fe55ca fix: linter; dont change doc after DB update (backport #49907) (#49909)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
fix: linter; dont change doc after DB update (#49907)
2025-10-06 13:34:24 +02:00
ruthra kumar
57f9894f41 Merge pull request #49892 from frappe/mergify/bp/version-14-hotfix/pr-49829
fix: Set paid amount automatically only if return entry validated and has negative grand total (backport #49829)
2025-10-06 12:58:55 +05:30
Nabin Hait
2c383a69f9 fix: resolved conflict 2025-10-06 12:19:22 +05:30
Nabin Hait
b7d76d8fad fix: Set paid amount automatically only if return entry validated and has negative grand total (#49829)
(cherry picked from commit dcbcc596f2)

# Conflicts:
#	erpnext/public/js/controllers/taxes_and_totals.js
2025-10-06 05:58:16 +00:00
mergify[bot]
9b5183e0e5 fix(Common Code): fetch canonical URI from Code List (backport #49882) (#49883)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Common Code): fetch canonical URI from Code List (#49882)
2025-10-04 19:22:04 +02:00
mergify[bot]
0b43d518af refactor(Supplier): custom buttons call make methods (backport #49840) (#49841)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-10-02 00:16:20 +02:00
Frappe PR Bot
a6b490e4b3 chore(release): Bumped to Version 14.91.3
## [14.91.3](https://github.com/frappe/erpnext/compare/v14.91.2...v14.91.3) (2025-09-30)

### Performance Improvements

* reposting for backdated transactions ([3d51c1b](3d51c1b363))
2025-09-30 13:07:25 +00:00
ruthra kumar
a5d33d5665 Merge pull request #49802 from frappe/version-14-hotfix
chore: release v14
2025-09-30 18:36:03 +05:30
rohitwaghchaure
14e92d589f Merge pull request #49723 from frappe/mergify/bp/version-14-hotfix/pr-49720
perf: reposting for backdated transactions (backport #49720)
2025-09-25 18:50:18 +05:30
Rohit Waghchaure
3d51c1b363 perf: reposting for backdated transactions
(cherry picked from commit 1b0fc0541b)
2025-09-25 12:16:23 +00:00
50 changed files with 463 additions and 218 deletions

View File

@@ -3,7 +3,7 @@ import inspect
import frappe
__version__ = "14.91.2"
__version__ = "14.92.14"
def get_default_company(user=None):

View File

@@ -9,13 +9,6 @@ cur_frm.add_fetch("bank", "swift_number", "swift_number");
frappe.ui.form.on("Bank Guarantee", {
setup: function (frm) {
frm.set_query("bank", function () {
return {
filters: {
company: frm.doc.company,
},
};
});
frm.set_query("bank_account", function () {
return {
filters: {

View File

@@ -19,7 +19,7 @@ frappe.ui.form.on("Currency Exchange Settings", {
to: "{to_currency}",
};
add_param(frm, r.message, params, result);
} else if (frm.doc.service_provider == "frankfurter.app") {
} else if (["frankfurter.app", "frankfurter.dev"].includes(frm.doc.service_provider)) {
let result = ["rates", "{to_currency}"];
let params = {
base: "{from_currency}",

View File

@@ -78,7 +78,7 @@
"fieldname": "service_provider",
"fieldtype": "Select",
"label": "Service Provider",
"options": "frankfurter.app\nexchangerate.host\nCustom",
"options": "frankfurter.dev\nexchangerate.host\nCustom",
"reqd": 1
},
{
@@ -104,7 +104,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-03-18 08:32:26.895076",
"modified": "2025-11-25 13:03:41.896424",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Currency Exchange Settings",
@@ -141,8 +141,9 @@
"write": 1
}
],
"sort_field": "modified",
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -35,7 +35,7 @@ class CurrencyExchangeSettings(Document):
self.append("req_params", {"key": "date", "value": "{transaction_date}"})
self.append("req_params", {"key": "from", "value": "{from_currency}"})
self.append("req_params", {"key": "to", "value": "{to_currency}"})
elif self.service_provider == "frankfurter.app":
elif self.service_provider in ("frankfurter.dev", "frankfurter.app"):
self.set("result_key", [])
self.set("req_params", [])
@@ -80,11 +80,13 @@ class CurrencyExchangeSettings(Document):
@frappe.whitelist()
def get_api_endpoint(service_provider: str | None = None, use_http: bool = False):
if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]:
if service_provider and service_provider in ["exchangerate.host", "frankfurter.dev", "frankfurter.app"]:
if service_provider == "exchangerate.host":
api = "api.exchangerate.host/convert"
elif service_provider == "frankfurter.app":
api = "api.frankfurter.app/{transaction_date}"
elif service_provider == "frankfurter.dev":
api = "api.frankfurter.dev/v1/{transaction_date}"
protocol = "https://"
if use_http:

View File

@@ -10,14 +10,19 @@
"description",
"section_break_4",
"due_date",
"invoice_portion",
"mode_of_payment",
"column_break_5",
"invoice_portion",
"due_date_based_on",
"credit_days",
"credit_months",
"section_break_6",
"discount_type",
"discount_date",
"column_break_9",
"discount",
"discount_type",
"column_break_9",
"discount_validity_based_on",
"discount_validity",
"section_break_9",
"payment_amount",
"outstanding",
@@ -155,12 +160,50 @@
"fieldtype": "Currency",
"label": "Payment Amount (Company Currency)",
"options": "Company:company:default_currency"
},
{
"fieldname": "due_date_based_on",
"fieldtype": "Select",
"label": "Due Date Based On",
"options": "\nDay(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
"read_only": 1
},
{
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
"fieldname": "credit_days",
"fieldtype": "Int",
"label": "Credit Days",
"non_negative": 1,
"read_only": 1
},
{
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
"fieldname": "credit_months",
"fieldtype": "Int",
"label": "Credit Months",
"non_negative": 1,
"read_only": 1
},
{
"depends_on": "discount",
"fieldname": "discount_validity_based_on",
"fieldtype": "Select",
"label": "Discount Validity Based On",
"options": "\nDay(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
"read_only": 1
},
{
"depends_on": "discount_validity_based_on",
"fieldname": "discount_validity",
"fieldtype": "Int",
"label": "Discount Validity",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-09-16 13:57:06.382859",
"modified": "2025-07-31 08:38:25.820701",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
@@ -171,4 +214,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -161,4 +161,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -11,9 +11,9 @@ frappe.ui.form.on("Period Closing Voucher", {
return {
filters: [
["Account", "company", "=", frm.doc.company],
["Account", "is_group", "=", "0"],
["Account", "is_group", "=", 0],
["Account", "freeze_account", "=", "No"],
["Account", "root_type", "in", "Liability, Equity"],
["Account", "root_type", "in", ["Liability", "Equity"]],
],
};
});

View File

@@ -449,7 +449,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

@@ -1816,19 +1816,16 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
self.assertAlmostEqual(rate, 500)
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_payment_allocation_for_payment_terms(self):
from erpnext.buying.doctype.purchase_order.test_purchase_order import (
create_pr_against_po,
create_purchase_order,
)
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
)
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
make_purchase_invoice as make_pi_from_pr,
)
automatically_fetch_payment_terms()
frappe.db.set_value(
"Payment Terms Template",
"_Test Payment Term Template",
@@ -1854,7 +1851,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
pi = make_pi_from_pr(pr.name)
self.assertEqual(pi.payment_schedule[0].payment_amount, 1000)
automatically_fetch_payment_terms(enable=0)
frappe.db.set_value(
"Payment Terms Template",
"_Test Payment Term Template",

View File

@@ -2828,6 +2828,60 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map)
def test_item_tax_template_change_with_grand_total_discount(self):
"""
Test that when item tax template changes due to discount on Grand Total,
the tax calculations are consistent.
"""
item = create_item("Test Item With Multiple Tax Templates")
item.set("taxes", [])
item.append(
"taxes",
{
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
"minimum_net_rate": 0,
"maximum_net_rate": 500,
},
)
item.append(
"taxes",
{
"item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
"minimum_net_rate": 501,
"maximum_net_rate": 1000,
},
)
item.save()
si = create_sales_invoice(item=item.name, rate=700, do_not_save=True)
si.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account Excise Duty - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Excise Duty",
"rate": 0,
},
)
si.insert()
self.assertEqual(si.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
si.apply_discount_on = "Grand Total"
si.discount_amount = 300
si.save()
# Verify template changed to 10%
self.assertEqual(si.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
self.assertEqual(si.taxes[0].tax_amount, 70) # 10% of 700
self.assertEqual(si.grand_total, 470) # 700 + 70 - 300
si.submit()
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
def test_sales_invoice_with_discount_accounting_enabled(self):
discount_account = create_account(

View File

@@ -80,7 +80,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "rate",
"fieldtype": "Int",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -102,7 +102,7 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -199,7 +199,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "amount",
"fieldtype": "Int",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -221,7 +221,7 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -324,7 +324,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-01-10 18:32:36.201124",
"modified": "2025-12-10 08:06:40.611761",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Share Balance",
@@ -339,4 +339,4 @@
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}
}

View File

@@ -52,28 +52,25 @@ def group_by_party_and_category(data, filters):
party_category_wise_map = {}
for row in data:
key = (row.get("party_type"), row.get("party"), row.get("tax_withholding_category"))
party_category_wise_map.setdefault(
(row.get("party"), row.get("section_code")),
key,
{
"pan": row.get("pan"),
"tax_id": row.get("tax_id"),
"party": row.get("party"),
"party_type": row.get("party_type"),
"party_name": row.get("party_name"),
"section_code": row.get("section_code"),
"entity_type": row.get("entity_type"),
"tax_withholding_category": row.get("tax_withholding_category"),
"party_entity_type": row.get("party_entity_type"),
"rate": row.get("rate"),
"total_amount": 0.0,
"tax_amount": 0.0,
},
)
party_category_wise_map.get((row.get("party"), row.get("section_code")))["total_amount"] += row.get(
"total_amount", 0.0
)
party_category_wise_map.get((row.get("party"), row.get("section_code")))["tax_amount"] += row.get(
"tax_amount", 0.0
)
party_category_wise_map.get(key)["total_amount"] += row.get("total_amount", 0.0)
party_category_wise_map.get(key)["tax_amount"] += row.get("tax_amount", 0.0)
final_result = get_final_result(party_category_wise_map)
@@ -114,13 +111,18 @@ def get_columns(filters):
columns.extend(
[
{
"label": _("Section Code"),
"label": _("Tax Withholding Category"),
"options": "Tax Withholding Category",
"fieldname": "section_code",
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
"width": 180,
},
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180},
{
"label": _("{0} Type").format(_(filters.get("party_type", "Party"))),
"fieldname": "party_entity_type",
"fieldtype": "Data",
"width": 180,
},
{
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
"fieldname": "rate",

View File

@@ -106,8 +106,8 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
row.update(
{
"section_code": tax_withholding_category or "",
"entity_type": party_map.get(party, {}).get(party_type),
"tax_withholding_category": tax_withholding_category or "",
"party_entity_type": party_map.get(party, {}).get(party_type),
"rate": rate,
"total_amount": total_amount,
"grand_total": grand_total,
@@ -127,7 +127,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
else:
entries[key] = row
out = list(entries.values())
out.sort(key=lambda x: (x["section_code"], x["transaction_date"]))
out.sort(key=lambda x: (x["tax_withholding_category"], x["transaction_date"], x["ref_no"]))
return out
@@ -177,9 +177,9 @@ def get_columns(filters):
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
columns = [
{
"label": _("Section Code"),
"label": _("Tax Withholding Category"),
"options": "Tax Withholding Category",
"fieldname": "section_code",
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
"width": 90,
},
@@ -208,7 +208,12 @@ def get_columns(filters):
columns.extend(
[
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
{
"label": _("{0} Type").format(_(filters.get("party_type", "Party"))),
"fieldname": "party_entity_type",
"fieldtype": "Data",
"width": 100,
},
]
)
if filters.party_type == "Supplier":

View File

@@ -118,7 +118,7 @@ class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
voucher_expected_values = expected_values[i]
voucher_actual_values = (
voucher.ref_no,
voucher.section_code,
voucher.tax_withholding_category,
voucher.rate,
voucher.base_total,
voucher.tax_amount,

View File

@@ -313,6 +313,7 @@ class Asset(AccountsController):
"asset_name": self.asset_name,
"target_location": self.location,
"to_employee": self.custodian,
"company": self.company,
}
]
asset_movement = frappe.get_doc(
@@ -1531,11 +1532,7 @@ def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx, number_of_
# if the Depreciation Schedule is being prepared for the first time
else:
if row.daily_prorata_based:
amount = (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
)
amount = flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
total_days = (
date_diff(
get_last_day(
@@ -1547,7 +1544,11 @@ def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx, number_of_
),
add_days(
get_last_day(
add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)
add_months(
row.depreciation_start_date,
(row.frequency_of_depreciation * (asset.number_of_depreciations_booked + 1))
* -1,
),
),
1,
),
@@ -1570,11 +1571,9 @@ def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx, number_of_
return daily_depr_amount * (date_diff(to_date, from_date) + 1)
else:
return (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
)
def get_shift_depr_amount(asset, row, schedule_idx):

View File

@@ -660,7 +660,7 @@ class TestDepreciationMethods(AssetSetup):
available_for_use_date="2030-06-06",
is_existing_asset=1,
number_of_depreciations_booked=2,
opening_accumulated_depreciation=47095.89,
opening_accumulated_depreciation=47178.08,
expected_value_after_useful_life=10000,
depreciation_start_date="2032-12-31",
total_number_of_depreciations=3,
@@ -668,7 +668,7 @@ class TestDepreciationMethods(AssetSetup):
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [["2032-12-31", 42904.11, 90000.0]]
expected_schedules = [["2032-12-31", 30000.0, 77178.08], ["2033-06-06", 12821.92, 90000.0]]
schedules = [
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in asset.get("schedules")

View File

@@ -456,9 +456,8 @@ class PurchaseOrder(BuyingController):
if not self.is_against_so():
return
for item in removed_items:
prev_ordered_qty = (
prev_ordered_qty = flt(
frappe.get_cached_value("Sales Order Item", item.get("sales_order_item"), "ordered_qty")
or 0.0
)
frappe.db.set_value(

View File

@@ -526,12 +526,8 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertRaises(frappe.ValidationError, pr.submit)
self.assertRaises(frappe.ValidationError, pi.submit)
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_make_purchase_invoice_with_terms(self):
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
)
automatically_fetch_payment_terms()
po = create_purchase_order(do_not_save=True)
self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name)
@@ -555,7 +551,6 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(getdate(pi.payment_schedule[0].due_date), getdate(po.transaction_date))
self.assertEqual(pi.payment_schedule[1].payment_amount, 2500.0)
self.assertEqual(getdate(pi.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30))
automatically_fetch_payment_terms(enable=0)
def test_warehouse_company_validation(self):
from erpnext.stock.utils import InvalidWarehouseCompany
@@ -703,6 +698,7 @@ class TestPurchaseOrder(FrappeTestCase):
)
self.assertEqual(due_date, "2023-03-31")
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 0})
def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self):
po = create_purchase_order(do_not_save=1)
po.payment_terms_template = "_Test Payment Term Template"
@@ -834,18 +830,16 @@ class TestPurchaseOrder(FrappeTestCase):
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 5)
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
)
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
compare_payment_schedules,
)
automatically_fetch_payment_terms()
po = create_purchase_order(qty=10, rate=100, do_not_save=1)
create_payment_terms_template()
po.payment_terms_template = "Test Receivable Template"
@@ -859,8 +853,6 @@ class TestPurchaseOrder(FrappeTestCase):
# self.assertEqual(po.payment_terms_template, pi.payment_terms_template)
compare_payment_schedules(self, po, pi)
automatically_fetch_payment_terms(enable=0)
def test_internal_transfer_flow(self):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
make_inter_company_purchase_invoice,

View File

@@ -89,21 +89,9 @@ frappe.ui.form.on("Supplier", {
__("View")
);
frm.add_custom_button(
__("Bank Account"),
function () {
erpnext.utils.make_bank_account(frm.doc.doctype, frm.doc.name);
},
__("Create")
);
frm.add_custom_button(__("Bank Account"), () => frm.make_methods["Bank Account"](), __("Create"));
frm.add_custom_button(
__("Pricing Rule"),
function () {
erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name);
},
__("Create")
);
frm.add_custom_button(__("Pricing Rule"), () => frm.make_methods["Pricing Rule"](), __("Create"));
frm.add_custom_button(
__("Get Supplier Group Details"),

View File

@@ -0,0 +1,11 @@
# End of Life reached
With the release of ERPNext version 16, version 14 has reached its end of life.
This means that version 14 of ERPNext (which is running on this site) will no longer receive critical bug fixes and is no longer covered under Frappe Support.
We highly recommend that you update to version 16 to get the latest bug fixes, features and other improvements.
[Click here to know more about version 16](https://frappe.io/erpnext/version-16)
If you're on [Frappe Cloud](https://frappe.io/cloud), [click here to learn how to update to v16](https://docs.frappe.io/cloud/sites/version-upgrade)

View File

@@ -213,6 +213,11 @@ class AccountsController(TransactionBase):
self.validate_date_with_fiscal_year()
self.validate_party_accounts()
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
if self.is_return:
self.validate_qty()
else:
self.validate_deferred_start_and_end_date()
self.validate_inter_company_reference()
@@ -258,11 +263,6 @@ class AccountsController(TransactionBase):
self.set_advance_gain_or_loss()
if self.is_return:
self.validate_qty()
else:
self.validate_deferred_start_and_end_date()
self.validate_deferred_income_expense_account()
self.set_inter_company_account()
@@ -2239,6 +2239,7 @@ class AccountsController(TransactionBase):
self.payment_schedule = []
self.payment_terms_template = po_or_so.payment_terms_template
posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date")
for schedule in po_or_so.payment_schedule:
payment_schedule = {
@@ -2251,6 +2252,17 @@ class AccountsController(TransactionBase):
}
if automatically_fetch_payment_terms:
if schedule.due_date_based_on:
payment_schedule["due_date"] = get_due_date(schedule, posting_date)
payment_schedule["due_date_based_on"] = schedule.due_date_based_on
payment_schedule["credit_days"] = cint(schedule.credit_days)
payment_schedule["credit_months"] = cint(schedule.credit_months)
if schedule.discount_validity_based_on:
payment_schedule["discount_date"] = get_discount_date(schedule, posting_date)
payment_schedule["discount_validity_based_on"] = schedule.discount_validity_based_on
payment_schedule["discount_validity"] = cint(schedule.discount_validity)
payment_schedule["payment_amount"] = flt(
grand_total * flt(payment_schedule["invoice_portion"]) / 100,
schedule.precision("payment_amount"),
@@ -2738,9 +2750,7 @@ def set_balance_in_account_currency(
_("Account: {0} with currency: {1} can not be selected").format(gl_dict.account, account_currency)
)
gl_dict["account_currency"] = (
company_currency if account_currency == company_currency else account_currency
)
gl_dict["account_currency"] = account_currency
# set debit/credit in account currency if not provided
if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency):
@@ -2987,14 +2997,26 @@ def get_payment_term_details(
term = frappe.get_doc("Payment Term", term)
else:
term_details.payment_term = term.payment_term
term_details.description = term.description
term_details.invoice_portion = term.invoice_portion
fields_to_copy = [
"description",
"invoice_portion",
"discount_type",
"discount",
"mode_of_payment",
"due_date_based_on",
"credit_days",
"credit_months",
"discount_validity_based_on",
"discount_validity",
]
for field in fields_to_copy:
term_details[field] = term.get(field)
term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100
term_details.discount_type = term.discount_type
term_details.discount = term.discount
term_details.outstanding = term_details.payment_amount
term_details.mode_of_payment = term.mode_of_payment
if bill_date:
term_details.due_date = get_due_date(term, bill_date)
@@ -3013,11 +3035,11 @@ def get_due_date(term, posting_date=None, bill_date=None):
due_date = None
date = bill_date or posting_date
if term.due_date_based_on == "Day(s) after invoice date":
due_date = add_days(date, term.credit_days)
due_date = add_days(date, cint(term.credit_days))
elif term.due_date_based_on == "Day(s) after the end of the invoice month":
due_date = add_days(get_last_day(date), term.credit_days)
due_date = add_days(get_last_day(date), cint(term.credit_days))
elif term.due_date_based_on == "Month(s) after the end of the invoice month":
due_date = get_last_day(add_months(date, term.credit_months))
due_date = get_last_day(add_months(date, cint(term.credit_months)))
return due_date
@@ -3025,11 +3047,11 @@ def get_discount_date(term, posting_date=None, bill_date=None):
discount_validity = None
date = bill_date or posting_date
if term.discount_validity_based_on == "Day(s) after invoice date":
discount_validity = add_days(date, term.discount_validity)
discount_validity = add_days(date, cint(term.discount_validity))
elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
discount_validity = add_days(get_last_day(date), term.discount_validity)
discount_validity = add_days(get_last_day(date), cint(term.discount_validity))
elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
discount_validity = get_last_day(add_months(date, term.discount_validity))
discount_validity = get_last_day(add_months(date, cint(term.discount_validity)))
return discount_validity

View File

@@ -728,7 +728,16 @@ class SellingController(StockController):
def set_default_income_account_for_item(obj):
for d in obj.get("items"):
if d.item_code:
if getattr(d, "income_account", None):
set_item_default(d.item_code, obj.company, "income_account", d.income_account)
"""Set income account as default for items in the transaction.
Updates the item default income account for each item in the transaction
if it differs from the company's default income account.
Args:
obj: Transaction document containing items table with income_account field
"""
company_default = frappe.get_cached_value("Company", obj.company, "default_income_account")
for d in obj.get("items", default=[]):
income_account = getattr(d, "income_account", None)
if d.item_code and income_account and income_account != company_default:
set_item_default(d.item_code, obj.company, "income_account", income_account)

View File

@@ -42,17 +42,23 @@ class calculate_taxes_and_totals:
items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items")))
return items
def calculate(self):
def calculate(self, ignore_tax_template_validation=False):
if not len(self._items):
return
self.discount_amount_applied = False
self.need_recomputation = False
self.ignore_tax_template_validation = ignore_tax_template_validation
self._calculate()
if self.doc.meta.get_field("discount_amount"):
self.set_discount_amount()
self.apply_discount_amount()
if not ignore_tax_template_validation and self.need_recomputation:
return self.calculate(ignore_tax_template_validation=True)
# Update grand total as per cash and non trade discount
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
self.doc.grand_total -= self.doc.discount_amount
@@ -96,6 +102,9 @@ class calculate_taxes_and_totals:
self.doc.base_tax_withholding_net_total = sum_base_net_amount
def validate_item_tax_template(self):
if self.ignore_tax_template_validation:
return
if self.doc.get("is_return") and self.doc.get("return_against"):
return
@@ -136,6 +145,10 @@ class calculate_taxes_and_totals:
)
)
# For correct tax_amount calculation re-computation is required
if self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total":
self.need_recomputation = True
def update_item_tax_map(self):
for item in self.doc.items:
item.item_tax_rate = get_item_tax_map(

View File

@@ -6,6 +6,7 @@
"engine": "InnoDB",
"field_order": [
"code_list",
"canonical_uri",
"title",
"common_code",
"description",
@@ -71,10 +72,17 @@
"in_list_view": 1,
"label": "Description",
"max_height": "60px"
},
{
"fetch_from": "code_list.canonical_uri",
"fieldname": "canonical_uri",
"fieldtype": "Data",
"label": "Canonical URI"
}
],
"grid_page_length": 50,
"links": [],
"modified": "2024-11-06 07:46:17.175687",
"modified": "2025-10-04 17:22:28.176155",
"modified_by": "Administrator",
"module": "EDI",
"name": "Common Code",
@@ -94,10 +102,11 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "common_code,description",
"show_title_field_in_link": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "title"
}
}

View File

@@ -22,6 +22,7 @@ class CommonCode(Document):
additional_data: DF.Code | None
applies_to: DF.Table[DynamicLink]
canonical_uri: DF.Data | None
code_list: DF.Link
common_code: DF.Data
description: DF.SmallText | None

View File

@@ -443,7 +443,7 @@ class JobCard(Document):
op_row.employee.append(time_log.employee)
if time_log.time_in_mins:
op_row.completed_time += time_log.time_in_mins
op_row.completed_qty += time_log.completed_qty
op_row.completed_qty += flt(time_log.completed_qty)
for row in self.sub_operations:
operation_deatils = operation_wise_completed_time.get(row.sub_operation)

View File

@@ -377,3 +377,4 @@ execute:frappe.db.set_single_value("Accounts Settings", "receivable_payable_fetc
erpnext.patches.v14_0.set_update_price_list_based_on
erpnext.patches.v14_0.rename_group_by_to_categorize_by_in_custom_reports
erpnext.patches.v14_0.update_full_name_in_contract
erpnext.patches.v16_0.update_currency_exchange_settings_for_frankfurter #2025-12-11

View File

@@ -2,6 +2,15 @@ import frappe
def execute():
try:
from erpnext.patches.v16_0.update_currency_exchange_settings_for_frankfurter import execute
execute()
except ImportError:
update_frankfurter_app_parameter_and_result()
def update_frankfurter_app_parameter_and_result():
settings = frappe.get_doc("Currency Exchange Settings")
if settings.service_provider != "frankfurter.app":
return

View File

@@ -0,0 +1,17 @@
import frappe
def execute():
settings_meta = frappe.get_meta("Currency Exchange Settings")
settings = frappe.get_doc("Currency Exchange Settings")
if (
"frankfurter.dev" not in settings_meta.get_options("service_provider").split("\n")
or settings.service_provider != "frankfurter.app"
):
return
settings.service_provider = "frankfurter.dev"
settings.set_parameters_and_result()
settings.flags.ignore_validate = True
settings.save()

View File

@@ -1,15 +1,17 @@
import urllib.parse
import frappe
def get_context(context):
if frappe.form_dict.project:
context.parents = [
{"title": frappe.form_dict.project, "route": "/projects?project=" + frappe.form_dict.project}
]
context.success_url = "/projects?project=" + frappe.form_dict.project
if project := frappe.form_dict.project:
title = frappe.utils.data.escape_html(project)
route = "/projects?" + urllib.parse.urlencode({"project": project})
context.parents = [{"title": title, "route": route}]
context.success_url = route
elif context.doc and context.doc.get("project"):
context.parents = [
{"title": context.doc.project, "route": "/projects?project=" + context.doc.project}
]
context.success_url = "/projects?project=" + context.doc.project
elif context.doc and (project := context.doc.get("project")):
title = frappe.utils.data.escape_html(project)
route = "/projects?" + urllib.parse.urlencode({"project": project})
context.parents = [{"title": title, "route": route}]
context.success_url = route

View File

@@ -76,9 +76,10 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
// Update paid amount on return/debit note creation
if (
this.frm.doc.doctype === "Purchase Invoice"
&& this.frm.doc.is_return
&& (this.frm.doc.grand_total > this.frm.doc.paid_amount)
this.frm.doc.doctype === "Purchase Invoice" &&
this.frm.doc.is_return &&
this.frm.doc.grand_total < 0 &&
this.frm.doc.grand_total > this.frm.doc.paid_amount
) {
this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total"));
}

View File

@@ -438,6 +438,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
item.weight_per_unit = 0;
item.weight_uom = '';
item.uom = null // make UOM blank to update the existing UOM when item changes
item.conversion_factor = 0;
if(['Sales Invoice'].includes(this.frm.doc.doctype)) {
@@ -878,6 +879,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
}
discount_date(doc, cdt, cdn) {
// Remove fields as discount_date is auto-managed by payment terms
const row = locals[cdt][cdn];
["discount_validity", "discount_validity_based_on"].forEach((field) => {
row[field] = "";
});
this.frm.refresh_field("payment_schedule");
}
due_date() {
// due_date is to be changed, payment terms template and/or payment schedule must
// be removed as due_date is automatically changed based on payment terms
@@ -1079,9 +1089,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
plc_conversion_rate() {
if(this.frm.doc.price_list_currency === this.get_company_currency()) {
this.frm.set_value("plc_conversion_rate", 1.0);
} else if(this.frm.doc.price_list_currency === this.frm.doc.currency
&& this.frm.doc.plc_conversion_rate && cint(this.frm.doc.plc_conversion_rate) != 1 &&
cint(this.frm.doc.plc_conversion_rate) != cint(this.frm.doc.conversion_rate)) {
} else if (
this.frm.doc.price_list_currency === this.frm.doc.currency &&
this.frm.doc.plc_conversion_rate &&
flt(this.frm.doc.plc_conversion_rate) != 1 &&
flt(this.frm.doc.plc_conversion_rate) != flt(this.frm.doc.conversion_rate)
) {
this.frm.set_value("conversion_rate", this.frm.doc.plc_conversion_rate);
}
@@ -2203,6 +2216,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
'item_code': item.item_code,
'valid_from': ["<=", doc.transaction_date || doc.bill_date || doc.posting_date],
'item_group': item.item_group,
'disabled': 0,
}
if (doc.tax_category)
@@ -2244,7 +2258,18 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
payment_term(doc, cdt, cdn) {
const me = this;
var row = locals[cdt][cdn];
if(row.payment_term) {
// empty date condition fields
[
"due_date_based_on",
"credit_days",
"credit_months",
"discount_validity",
"discount_validity_based_on",
].forEach(function (field) {
row[field] = "";
});
if (row.payment_term) {
frappe.call({
method: "erpnext.controllers.accounts_controller.get_payment_term_details",
args: {
@@ -2254,16 +2279,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total,
base_grand_total: this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total
},
callback: function(r) {
if(r.message && !r.exc) {
for (var d in r.message) {
frappe.model.set_value(cdt, cdn, d, r.message[d]);
const company_currency = me.get_company_currency();
me.update_payment_schedule_grid_labels(company_currency);
callback: function (r) {
if (r.message && !r.exc) {
const company_currency = me.get_company_currency();
for (let d in r.message) {
row[d] = r.message[d];
}
me.update_payment_schedule_grid_labels(company_currency);
me.frm.refresh_field("payment_schedule");
}
}
})
},
});
} else {
me.frm.refresh_field("payment_schedule");
}
}

View File

@@ -381,7 +381,7 @@ erpnext.SerialNoBatchSelector = class SerialNoBatchSelector {
query: "erpnext.controllers.queries.get_batch_no",
};
},
onchange: function () {
change: function () {
const batch_no = this.get_value();
if (!batch_no) {
this.grid_row.on_grid_fields_dict.available_qty.set_value(0);

View File

@@ -10,7 +10,7 @@ from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.controllers.accounts_controller import update_child_qty_rate
from erpnext.controllers.accounts_controller import get_due_date, update_child_qty_rate
from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import (
make_maintenance_schedule,
)
@@ -1759,14 +1759,13 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
so.load_from_db()
self.assertRaises(frappe.LinkExistsError, so.cancel)
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
automatically_fetch_payment_terms()
so = make_sales_order(uom="Nos", do_not_save=1)
create_payment_terms_template()
so.payment_terms_template = "Test Receivable Template"
@@ -1780,8 +1779,6 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
self.assertEqual(so.payment_terms_template, si.payment_terms_template)
compare_payment_schedules(self, so, si)
automatically_fetch_payment_terms(enable=0)
def test_zero_amount_sales_order_billing_status(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -2284,16 +2281,14 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
self.assertRaises(frappe.ValidationError, so1.update_status, "Draft")
def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.automatically_fetch_payment_terms = enable
accounts_settings.save()
def compare_payment_schedules(doc, doc1, doc2):
for index, schedule in enumerate(doc1.get("payment_schedule")):
posting_date = doc1.get("bill_date") or doc1.get("posting_date") or doc1.get("transaction_date")
due_date = schedule.due_date
if schedule.due_date_based_on:
due_date = get_due_date(schedule, posting_date=posting_date)
doc.assertEqual(schedule.payment_term, doc2.payment_schedule[index].payment_term)
doc.assertEqual(getdate(schedule.due_date), doc2.payment_schedule[index].due_date)
doc.assertEqual(due_date, doc2.payment_schedule[index].due_date)
doc.assertEqual(schedule.invoice_portion, doc2.payment_schedule[index].invoice_portion)
doc.assertEqual(schedule.payment_amount, doc2.payment_schedule[index].payment_amount)

View File

@@ -37,15 +37,15 @@ class SellingSettings(Document):
)
def toggle_hide_tax_id(self):
self.hide_tax_id = cint(self.hide_tax_id)
_hide_tax_id = cint(self.hide_tax_id)
# Make property setters to hide tax_id fields
for doctype in ("Sales Order", "Sales Invoice", "Delivery Note"):
make_property_setter(
doctype, "tax_id", "hidden", self.hide_tax_id, "Check", validate_fields_for_doctype=False
doctype, "tax_id", "hidden", _hide_tax_id, "Check", validate_fields_for_doctype=False
)
make_property_setter(
doctype, "tax_id", "print_hide", self.hide_tax_id, "Check", validate_fields_for_doctype=False
doctype, "tax_id", "print_hide", _hide_tax_id, "Check", validate_fields_for_doctype=False
)
def toggle_editable_rate_for_bundle_items(self):

View File

@@ -11,7 +11,7 @@ from frappe.cache_manager import clear_defaults_cache
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.desk.page.setup_wizard.setup_wizard import make_records
from frappe.utils import cint, formatdate, get_link_to_form, get_timestamp, today
from frappe.utils import add_months, cint, formatdate, get_first_day, get_link_to_form, get_timestamp, today
from frappe.utils.nestedset import NestedSet, rebuild_tree
from erpnext.accounts.doctype.account.account import get_account_currency
@@ -614,27 +614,29 @@ def install_country_fixtures(company, country):
def update_company_current_month_sales(company):
current_month_year = formatdate(today(), "MM-yyyy")
from_date = get_first_day(today())
to_date = get_first_day(add_months(from_date, 1))
results = frappe.db.sql(
f"""
"""
SELECT
SUM(base_grand_total) AS total,
DATE_FORMAT(`posting_date`, '%m-%Y') AS month_year
DATE_FORMAT(posting_date, '%%m-%%Y') AS month_year
FROM
`tabSales Invoice`
WHERE
DATE_FORMAT(`posting_date`, '%m-%Y') = '{current_month_year}'
posting_date >= %s
AND posting_date < %s
AND docstatus = 1
AND company = {frappe.db.escape(company)}
AND company = %s
GROUP BY
month_year
""",
""",
(from_date, to_date, company),
as_dict=True,
)
monthly_total = results[0]["total"] if len(results) > 0 else 0
frappe.db.set_value("Company", company, "total_monthly_sales", monthly_total)

View File

@@ -68,9 +68,9 @@ def patched_requests_get(*args, **kwargs):
if kwargs["params"].get("date") and kwargs["params"].get("from") and kwargs["params"].get("to"):
if test_exchange_values.get(kwargs["params"]["date"]):
return PatchResponse({"result": test_exchange_values[kwargs["params"]["date"]]}, 200)
elif args[0].startswith("https://api.frankfurter.app") and kwargs.get("params"):
elif args[0].startswith("https://api.frankfurter.dev") and kwargs.get("params"):
if kwargs["params"].get("base") and kwargs["params"].get("symbols"):
date = args[0].replace("https://api.frankfurter.app/", "")
date = args[0].replace("https://api.frankfurter.dev/v1/", "")
if test_exchange_values.get(date):
return PatchResponse(
{"rates": {kwargs["params"].get("symbols"): test_exchange_values.get(date)}}, 200
@@ -149,7 +149,7 @@ class TestCurrencyExchange(unittest.TestCase):
self.assertEqual(flt(exchange_rate, 3), 65.1)
settings = frappe.get_single("Currency Exchange Settings")
settings.service_provider = "frankfurter.app"
settings.service_provider = "frankfurter.dev"
settings.save()
def test_exchange_rate_strict(self, mock_get):

View File

@@ -309,8 +309,9 @@ class TransactionDeletionRecord(Document):
self.db_set("error_log", None)
def get_doctypes_to_be_ignored_list(self):
singles = frappe.get_all("DocType", filters={"issingle": 1}, pluck="name")
doctypes_to_be_ignored_list = singles
doctypes_to_be_ignored_list = frappe.get_all(
"DocType", or_filters=[["issingle", "=", 1], ["is_virtual", "=", 1]], pluck="name"
)
for doctype in self.doctypes_to_be_ignored:
doctypes_to_be_ignored_list.append(doctype.doctype_name)

View File

@@ -80,7 +80,7 @@ def setup_currency_exchange():
ces.set("result_key", [])
ces.set("req_params", [])
ces.api_endpoint = "https://api.frankfurter.app/{transaction_date}"
ces.api_endpoint = "https://api.frankfurter.dev/v1/{transaction_date}"
ces.append("result_key", {"key": "rates"})
ces.append("result_key", {"key": "{to_currency}"})
ces.append("req_params", {"key": "base", "value": "{from_currency}"})

View File

@@ -13,7 +13,6 @@ from erpnext.accounts.utils import get_balance_on
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
compare_payment_schedules,
create_dn_against_so,
make_sales_order,
@@ -1026,14 +1025,13 @@ class TestDeliveryNote(FrappeTestCase):
self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item")
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
automatically_fetch_payment_terms()
so = make_sales_order(uom="Nos", do_not_save=1)
create_payment_terms_template()
so.payment_terms_template = "Test Receivable Template"
@@ -1053,8 +1051,6 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(so.payment_terms_template, si.payment_terms_template)
compare_payment_schedules(self, so, si)
automatically_fetch_payment_terms(enable=0)
def test_returned_qty_in_return_dn(self):
# SO ---> SI ---> DN
# |
@@ -1511,6 +1507,23 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(stock_value_difference, 100.0 * 5)
def test_negative_stock_with_higher_precision(self):
original_flt_precision = frappe.db.get_default("float_precision")
frappe.db.set_single_value("System Settings", "float_precision", 7)
item_code = make_item(
"Test Negative Stock High Precision Item", properties={"is_stock_item": 1, "valuation_rate": 1}
).name
dn = create_delivery_note(
item_code=item_code,
qty=0.0000010,
do_not_submit=True,
)
self.assertRaises(frappe.ValidationError, dn.submit)
frappe.db.set_single_value("System Settings", "float_precision", original_flt_precision)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")

View File

@@ -686,18 +686,14 @@ def get_available_item_locations(
locations = get_available_item_locations_for_batched_item(
item_code,
from_warehouses,
required_qty,
company,
total_picked_qty,
consider_rejected_warehouses=consider_rejected_warehouses,
)
else:
locations = get_available_item_locations_for_other_item(
item_code,
from_warehouses,
required_qty,
company,
total_picked_qty,
consider_rejected_warehouses=consider_rejected_warehouses,
)
@@ -790,9 +786,7 @@ def get_available_item_locations_for_serialized_item(
def get_available_item_locations_for_batched_item(
item_code,
from_warehouses,
required_qty,
company,
total_picked_qty=0,
consider_rejected_warehouses=False,
):
sle = frappe.qb.DocType("Stock Ledger Entry")
@@ -813,7 +807,6 @@ def get_available_item_locations_for_batched_item(
.groupby(sle.warehouse, sle.batch_no, sle.item_code)
.having(Sum(sle.actual_qty) > 0)
.orderby(IfNull(batch.expiry_date, "2200-01-01"), batch.creation, sle.batch_no, sle.warehouse)
.limit(ceil(required_qty + total_picked_qty))
)
if from_warehouses:
@@ -838,7 +831,6 @@ def get_available_item_locations_for_serial_and_batched_item(
locations = get_available_item_locations_for_batched_item(
item_code,
from_warehouses,
required_qty,
company,
consider_rejected_warehouses=consider_rejected_warehouses,
)
@@ -872,9 +864,7 @@ def get_available_item_locations_for_serial_and_batched_item(
def get_available_item_locations_for_other_item(
item_code,
from_warehouses,
required_qty,
company,
total_picked_qty=0,
consider_rejected_warehouses=False,
):
bin = frappe.qb.DocType("Bin")
@@ -883,7 +873,6 @@ def get_available_item_locations_for_other_item(
.select(bin.warehouse, bin.actual_qty.as_("qty"))
.where((bin.item_code == item_code) & (bin.actual_qty > 0))
.orderby(bin.creation)
.limit(ceil(required_qty + total_picked_qty))
)
if from_warehouses:

View File

@@ -1056,6 +1056,7 @@ class TestPurchaseReceipt(FrappeTestCase):
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
@@ -1066,12 +1067,9 @@ class TestPurchaseReceipt(FrappeTestCase):
make_pr_against_po,
)
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
compare_payment_schedules,
)
automatically_fetch_payment_terms()
po = create_purchase_order(qty=10, rate=100, do_not_save=1)
create_payment_terms_template()
po.payment_terms_template = "Test Receivable Template"
@@ -1089,8 +1087,6 @@ class TestPurchaseReceipt(FrappeTestCase):
# self.assertEqual(po.payment_terms_template, pi.payment_terms_template)
compare_payment_schedules(self, po, pi)
automatically_fetch_payment_terms(enable=0)
@change_settings("Stock Settings", {"allow_negative_stock": 1})
def test_neg_to_positive(self):
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry

View File

@@ -193,7 +193,7 @@ class SerialNo(StockController):
entries["last_sle"] = last_sle
if sle_dict.get("incoming", []):
entries["purchase_sle"] = sle_dict["incoming"][-1]
entries["purchase_sle"] = sle_dict["incoming"][0]
if last_sle.get("actual_qty") < 0 and sle_dict.get("outgoing", []):
entries["delivery_sle"] = sle_dict["outgoing"][0]

View File

@@ -13,7 +13,7 @@ from erpnext.controllers.stock_controller import StockController
from erpnext.stock.doctype.batch.batch import get_batch_qty
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.utils import get_stock_balance
from erpnext.stock.utils import get_combine_datetime, get_stock_balance
class OpeningEntryAccountError(frappe.ValidationError):
@@ -646,6 +646,7 @@ class StockReconciliation(StockController):
self.posting_date,
self.posting_time,
self.name,
sle_creation,
)
precesion = row.precision("current_qty")
@@ -705,8 +706,11 @@ class StockReconciliation(StockController):
return allow_negative_stock
def get_batch_qty_for_stock_reco(item_code, warehouse, batch_no, posting_date, posting_time, voucher_no):
def get_batch_qty_for_stock_reco(
item_code, warehouse, batch_no, posting_date, posting_time, voucher_no, sle_creation
):
ledger = frappe.qb.DocType("Stock Ledger Entry")
posting_datetime = get_combine_datetime(posting_date, posting_time)
query = (
frappe.qb.from_(ledger)
@@ -719,12 +723,11 @@ def get_batch_qty_for_stock_reco(item_code, warehouse, batch_no, posting_date, p
& (ledger.docstatus == 1)
& (ledger.is_cancelled == 0)
& (ledger.batch_no == batch_no)
& (ledger.posting_date <= posting_date)
& (
CombineDatetime(ledger.posting_date, ledger.posting_time)
<= CombineDatetime(posting_date, posting_time)
)
& (ledger.voucher_no != voucher_no)
& (
(ledger.posting_datetime < posting_datetime)
| ((ledger.posting_datetime == posting_datetime) & (ledger.creation < sle_creation))
)
)
.groupby(ledger.batch_no)
)

View File

@@ -635,8 +635,10 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
taxes_with_no_validity = []
for tax in taxes:
tax_company = frappe.get_cached_value("Item Tax Template", tax.item_tax_template, "company")
if tax_company == args["company"]:
disabled, tax_company = frappe.get_cached_value(
"Item Tax Template", tax.item_tax_template, ["disabled", "company"]
)
if not disabled and tax_company == args["company"]:
if tax.valid_from or tax.maximum_net_rate:
# In purchase Invoice first preference will be given to supplier invoice date
# if supplier date is not present then posting date

View File

@@ -41,9 +41,37 @@ def get_data(report_filters):
gl_data = voucher_wise_gl_data.get(key) or {}
d.account_value = gl_data.get("account_value", 0)
d.difference_value = d.stock_value - d.account_value
d.ledger_type = "Stock Ledger Entry"
if abs(d.difference_value) > 0.1:
data.append(d)
if key in voucher_wise_gl_data:
del voucher_wise_gl_data[key]
if voucher_wise_gl_data:
data += get_gl_ledgers_with_no_stock_ledger_entries(voucher_wise_gl_data)
return data
def get_gl_ledgers_with_no_stock_ledger_entries(voucher_wise_gl_data):
data = []
for key in voucher_wise_gl_data:
gl_data = voucher_wise_gl_data.get(key) or {}
data.append(
{
"name": gl_data.get("name"),
"ledger_type": "GL Entry",
"voucher_type": gl_data.get("voucher_type"),
"voucher_no": gl_data.get("voucher_no"),
"posting_date": gl_data.get("posting_date"),
"stock_value": 0,
"account_value": gl_data.get("account_value", 0),
"difference_value": gl_data.get("account_value", 0) * -1,
}
)
return data
@@ -88,6 +116,7 @@ def get_gl_data(report_filters, filters):
"voucher_type",
"voucher_no",
"sum(debit_in_account_currency) - sum(credit_in_account_currency) as account_value",
"posting_date",
],
group_by="voucher_type, voucher_no",
)
@@ -105,10 +134,15 @@ def get_columns(filters):
{
"label": _("Stock Ledger ID"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Stock Ledger Entry",
"fieldtype": "Dynamic Link",
"options": "ledger_type",
"width": "80",
},
{
"label": _("Ledger Type"),
"fieldname": "ledger_type",
"fieldtype": "Data",
},
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date"},
{"label": _("Posting Time"), "fieldname": "posting_time", "fieldtype": "Time"},
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": "110"},

View File

@@ -496,7 +496,10 @@ class update_entries_after:
elif dependant_sle.voucher_type == "Stock Entry" and is_transfer_stock_entry(
dependant_sle.voucher_no
):
print(dependant_sle.voucher_no)
if self.distinct_item_warehouses[key].get("transfer_entry_to_repost"):
return
val["transfer_entry_to_repost"] = True
self.distinct_item_warehouses[key] = val
self.new_items_found = True
@@ -712,7 +715,11 @@ class update_entries_after:
diff = self.wh_data.qty_after_transaction + flt(sle.actual_qty)
diff = flt(diff, self.flt_precision) # respect system precision
if diff < 0 and abs(diff) > 0.0001:
diff_threshold = 0.0001
if self.flt_precision > 4:
diff_threshold = 10 ** (-1 * self.flt_precision)
if diff < 0 and abs(diff) > diff_threshold:
# negative stock!
exc = sle.copy().update({"diff": diff})
self.exceptions.setdefault(sle.warehouse, []).append(exc)
@@ -984,7 +991,7 @@ class update_entries_after:
# else it remains the same as that of previous entry
self.wh_data.valuation_rate = new_stock_value / new_stock_qty
if not self.wh_data.valuation_rate and sle.voucher_detail_no:
if self.wh_data.valuation_rate is None and sle.voucher_detail_no:
allow_zero_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
if not allow_zero_rate:
self.wh_data.valuation_rate = self.get_fallback_rate(sle)

View File

@@ -158,7 +158,7 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord
customer = contact_doc.get_link_for("Customer")
ignore_permissions = False
if is_website_user():
if is_website_user() and user != "Guest":
if not filters:
filters = {}

View File

@@ -449,10 +449,16 @@ def get_documents_with_active_service_level_agreement():
def set_documents_with_active_service_level_agreement():
active = [
sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
]
frappe.cache().hset("service_level_agreement", "active", active)
try:
active = frozenset(
sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
)
frappe.cache().hset("service_level_agreement", "active", active)
except (frappe.DoesNotExistError, frappe.db.TableMissingError):
# This happens during install / uninstall when wildcard hook for SLA intercepts some doc action.
# In both cases, the error can be safely ignored.
active = frozenset()
return active