Compare commits

..

94 Commits

Author SHA1 Message Date
Frappe PR Bot
8b9788ca74 chore(release): Bumped to Version 15.83.2
## [15.83.2](https://github.com/frappe/erpnext/compare/v15.83.1...v15.83.2) (2025-10-18)

### Bug Fixes

* internal transfer entry with serial/batch ([97cdac1](97cdac10d7))
* **stock:** remove duplicate fields ([5e8e6ef](5e8e6ef2f3))
2025-10-18 04:42:12 +00:00
rohitwaghchaure
eb38bcdab7 Merge pull request #50164 from frappe/mergify/bp/version-15/pr-50158
fix: internal transfer entry with serial/batch (backport #50156) (backport #50158)
2025-10-18 10:10:35 +05:30
rohitwaghchaure
9876d1c7e6 Merge pull request #50165 from frappe/mergify/bp/version-15/pr-50157
fix(stock-settings): remove duplicate fields (backport #50157)
2025-10-18 10:10:20 +05:30
Kavin
5e8e6ef2f3 fix(stock): remove duplicate fields
(cherry picked from commit 58a1383380)
2025-10-18 03:49:50 +00:00
Rohit Waghchaure
97cdac10d7 fix: internal transfer entry with serial/batch
(cherry picked from commit 9b4e62a758)
(cherry picked from commit d67a439051)
2025-10-18 03:47:47 +00:00
Frappe PR Bot
5e21c9c5c9 chore(release): Bumped to Version 15.83.1
## [15.83.1](https://github.com/frappe/erpnext/compare/v15.83.0...v15.83.1) (2025-10-17)

### Bug Fixes

* validation for negative batch ([555d5da](555d5da611))
2025-10-17 09:19:51 +00:00
rohitwaghchaure
6262566c53 Merge pull request #50150 from frappe/mergify/bp/version-15/pr-50145
fix: validation for negative batch (backport #50123) (backport #50145)
2025-10-17 14:48:04 +05:30
Rohit Waghchaure
555d5da611 fix: validation for negative batch
(cherry picked from commit f9c8f27586)
(cherry picked from commit b9dd05f292)
2025-10-17 08:06:54 +00:00
Frappe PR Bot
779074e6a6 chore(release): Bumped to Version 15.83.0
# [15.83.0](https://github.com/frappe/erpnext/compare/v15.82.2...v15.83.0) (2025-10-14)

### Bug Fixes

* add GROUP BY for dn_detail and convert SQL query to QB ([9aa9b18](9aa9b181e5))
* **asset movement:** clear custodian if not present ([4ec5b28](4ec5b28fd2))
* Batch ordering based on the method mentioned in settings ([50266d3](50266d3b6b))
* batch qty for expired batches ([f4816e4](f4816e4960))
* consider negative qty in batch qty calculation ([4370a59](4370a59183))
* **deferred revenue:** validate service stop date ([557d53a](557d53a953))
* do reposting of first transfer entry based on item-wh combination ([e9d71e0](e9d71e013a))
* duplicate serial nos ([9854ded](9854dedc06))
* enhance sub-assembly item handling in raw material request calculations ([467fcea](467fcea728))
* filter sales team to show only active individual salespersons ([38efd5c](38efd5cb0b))
* fixed asset register showing opening entries ([1ea6e1d](1ea6e1db12))
* hide sales invoice creation for fully returned delivery notes ([b426b8c](b426b8c07f))
* incorrect field valuation_rate ([93df11a](93df11a0cf))
* negative error not throw for backdated entry ([1fc21d6](1fc21d60c6))
* performance issue by adding index ([e4fd49e](e4fd49e991))
* preserve address if present ([aaf470c](aaf470cf5c))
* prevent empty Create dropdown when In Process (backport [#49891](https://github.com/frappe/erpnext/issues/49891)) ([#50063](https://github.com/frappe/erpnext/issues/50063)) ([b67b292](b67b29200c))
* **production plan:** filter sales orders by item ([20c2809](20c2809437))
* reset raw materials considering not available batches ([2184a28](2184a28e91))
* Reset Raw Materials Table button not working ([81ed32f](81ed32ff51))
* resolve conflict ([dccc561](dccc561eec))
* resolve conflict ([38e1ca1](38e1ca1362))
* revert unrelated manual modified timestamp change ([c4cba78](c4cba78124))
* sales return for product bundle items ([ac46b3d](ac46b3d1ca))
* sanitize projects field in tasks webform ([#50089](https://github.com/frappe/erpnext/issues/50089)) ([432201f](432201f634))
* set default roles on role_profile during reinstallation ([c93fbf3](c93fbf3982))
* skip auto-cancel of depreciation for components during asset capitalization ([6d5f2b5](6d5f2b5024))
* skip party validation for payroll & it's journal & GL entry submission (backport [#49638](https://github.com/frappe/erpnext/issues/49638)) ([#49826](https://github.com/frappe/erpnext/issues/49826)) ([957b47f](957b47f351))
* stock ledger adjustment entry ([8020159](8020159c14))
* **stock-entry:** fetch empty batch for finished item ([af3d7ef](af3d7ef300))
* **stock-reconciliation:** include inventory dimensions in duplicate validation ([21a972a](21a972ad95))
* **Supplier Quotation Comparison:** add a missing translate function (backport [#49497](https://github.com/frappe/erpnext/issues/49497)) ([#50055](https://github.com/frappe/erpnext/issues/50055)) ([b7c2405](b7c2405113))
* swap warehouse labels for return entry ([c5dc810](c5dc810642))
* warehouse source reference in production report ([db93e50](db93e50f16))

### Features

* add asset name to Asset Depreciations and Balances report ([0776b30](0776b300e8))

### Performance Improvements

* add composite indexes to Advance Payment Ledger Entry table ([5652e92](5652e926d7))
* optimize sql query ([79a8e26](79a8e2656b))
2025-10-14 14:03:31 +00:00
Diptanil Saha
c521a6495d Merge pull request #50087 from frappe/version-15-hotfix
chore: release v15
2025-10-14 19:27:40 +05:30
rohitwaghchaure
6f02362765 Merge pull request #50098 from frappe/mergify/bp/version-15-hotfix/pr-50089
fix: sanitize projects field in tasks webform (backport #50089)
2025-10-14 19:07:50 +05:30
Akhil Narang
432201f634 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:51 +00:00
rohitwaghchaure
f9dc00f8ad Merge pull request #50094 from frappe/mergify/bp/version-15-hotfix/pr-50080
perf: optimize sql query (backport #50080)
2025-10-14 18:17:00 +05:30
rohitwaghchaure
0dd79e8c2b Merge pull request #50092 from frappe/mergify/bp/version-15-hotfix/pr-50091
fix: negative error not throw for backdated entry (backport #50091)
2025-10-14 18:16:42 +05:30
Khushi Rawat
f70e61ab31 Merge pull request #50093 from frappe/mergify/bp/version-15-hotfix/pr-50033
fix: filter sales team to show only active individual salespersons (backport #50033)
2025-10-14 17:51:15 +05:30
mergify[bot]
b67b29200c fix: prevent empty Create dropdown when In Process (backport #49891) (#50063) 2025-10-14 17:49:27 +05:30
mergify[bot]
fd72b55852 Merge pull request #50081 from frappe/mergify/bp/version-15-hotfix/pr-49960
Fix/Support 50220 (backport #49960)
2025-10-14 12:12:14 +00:00
Khushi Rawat
dccc561eec fix: resolve conflict 2025-10-14 17:25:48 +05:30
rohitwaghchaure
8bf553bbce chore: fix conflicts 2025-10-14 17:17:12 +05:30
Rohit Waghchaure
79a8e2656b perf: optimize sql query
(cherry picked from commit e7b64175fd)

# Conflicts:
#	erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
2025-10-14 11:44:04 +00:00
Rehan Ansari
38efd5cb0b fix: filter sales team to show only active individual salespersons
(cherry picked from commit 2fcd406b18)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.js
2025-10-14 11:40:00 +00:00
Rohit Waghchaure
1fc21d60c6 fix: negative error not throw for backdated entry
(cherry picked from commit 88a947ff4e)
2025-10-14 11:39:06 +00:00
Khushi Rawat
a777a11919 Merge pull request #50088 from khushi8112/composite-component-depr-jv-cancellation-issue
fix: skip auto-cancel of depreciation for components during asset capitalization
2025-10-14 16:57:50 +05:30
Diptanil Saha
6cc046362f Merge branch 'version-15' into version-15-hotfix 2025-10-14 16:56:32 +05:30
khushi8112
02ebde43bf refactor: Ensure flag cleanup with try-finally to prevent state corruption 2025-10-14 16:32:32 +05:30
khushi8112
6d5f2b5024 fix: skip auto-cancel of depreciation for components during asset capitalization 2025-10-14 15:19:07 +05:30
Mihir Kandoi
a50313f990 Merge pull request #50084 from frappe/mergify/bp/version-15-hotfix/pr-50004
fix: add GROUP BY for dn_detail and convert SQL query to QB (backport #50004)
2025-10-14 15:13:59 +05:30
Mihir Kandoi
7d533b7086 chore: resolve conflicts 2025-10-14 14:57:28 +05:30
l0gesh29
9aa9b181e5 fix: add GROUP BY for dn_detail and convert SQL query to QB
(cherry picked from commit fd9167f2af)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/delivery_note.py
2025-10-14 09:20:17 +00:00
l0gesh29
b426b8c07f fix: hide sales invoice creation for fully returned delivery notes
(cherry picked from commit 1f831d8783)
2025-10-14 09:20:16 +00:00
Mihir Kandoi
6682a25532 Merge pull request #50082 from frappe/mergify/bp/version-15-hotfix/pr-50079
chore: replace broken links with correct ones (backport #50079)
2025-10-14 14:43:05 +05:30
Mihir Kandoi
01fe1c658e chore: replace broken links with correct ones
(cherry picked from commit 11be07086f)
2025-10-14 08:58:06 +00:00
rohitwaghchaure
82e392bc0c Merge pull request #50074 from frappe/mergify/bp/version-15-hotfix/pr-50070
fix: performance issue by adding index (backport #50070)
2025-10-14 13:57:10 +05:30
rohitwaghchaure
b6f99a127b Merge pull request #50075 from frappe/mergify/bp/version-15-hotfix/pr-50065
fix(stock-reconciliation): include inventory dimensions in duplicate validation (backport #50065)
2025-10-14 13:56:55 +05:30
rohitwaghchaure
7d34a39137 Merge pull request #50076 from frappe/mergify/bp/version-15-hotfix/pr-50073
fix(stock-entry): fetch empty batch for finished item (backport #50073)
2025-10-14 13:56:41 +05:30
rohitwaghchaure
76fe4e26ea Merge pull request #50077 from frappe/mergify/bp/version-15-hotfix/pr-50072
fix: swap warehouse labels for return entry (backport #50072)
2025-10-14 13:56:17 +05:30
Kavin
c5dc810642 fix: swap warehouse labels for return entry
(cherry picked from commit f0c3f0d0be)
2025-10-14 08:07:00 +00:00
venkat102
af3d7ef300 fix(stock-entry): fetch empty batch for finished item
(cherry picked from commit 74a7ddf66d)
2025-10-14 08:05:57 +00:00
rohitwaghchaure
61f7309695 chore: fix conflicts 2025-10-14 13:33:56 +05:30
Rohit Waghchaure
9854dedc06 fix: duplicate serial nos
(cherry picked from commit c95465cba1)
2025-10-14 08:03:44 +00:00
venkat102
21a972ad95 fix(stock-reconciliation): include inventory dimensions in duplicate validation
(cherry picked from commit 4b21c2cc46)
2025-10-14 08:03:43 +00:00
rohitwaghchaure
967ee78415 chore: fix conflicts 2025-10-14 13:33:24 +05:30
Rohit Waghchaure
e4fd49e991 fix: performance issue by adding index
(cherry picked from commit 1afc75b15a)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
#	erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
2025-10-14 08:02:28 +00:00
rohitwaghchaure
b82f8648f1 Merge pull request #50068 from frappe/mergify/bp/version-15-hotfix/pr-50061
fix: do reposting of first transfer entry based on item-wh combination (backport #50061)
2025-10-14 13:21:13 +05:30
rohitwaghchaure
141b70ce8b Merge pull request #50069 from frappe/mergify/bp/version-15-hotfix/pr-50027
fix: reset raw materials considering not available batches (backport #50027)
2025-10-14 13:21:03 +05:30
Rohit Waghchaure
2184a28e91 fix: reset raw materials considering not available batches
(cherry picked from commit ec1636db12)
2025-10-14 07:19:43 +00:00
Rohit Waghchaure
e9d71e013a fix: do reposting of first transfer entry based on item-wh combination
(cherry picked from commit 2f25b445ab)
2025-10-14 07:19:08 +00:00
Mihir Kandoi
5909b0e1a2 Merge pull request #50064 from frappe/mergify/bp/version-15-hotfix/pr-49445
fix(production plan): filter sales orders by item (backport #49445)
2025-10-14 12:20:56 +05:30
Mihir Kandoi
525dd42e86 Merge pull request #50062 from frappe/mergify/bp/version-15-hotfix/pr-50058
fix: warehouse source reference in production report (backport #50058)
2025-10-14 12:10:53 +05:30
ravibharathi656
20c2809437 fix(production plan): filter sales orders by item
(cherry picked from commit bfff945fb1)
2025-10-14 06:32:58 +00:00
matteo.arosti
db93e50f16 fix: warehouse source reference in production report
(cherry picked from commit 451651e350)
2025-10-14 06:22:44 +00:00
mergify[bot]
957b47f351 fix: skip party validation for payroll & it's journal & GL entry submission (backport #49638) (#49826)
* fix: skip party validation for payroll & it's journal & GL entry submission (#49638)

* fix: skip validation for manual je & gl submission linked with payroll entry

* refactor: change condition

* fix: add checkbox in jouranl entry account and passed it true from payroll to skip party validation

* refactor: add checkbox to skip party validation in journal entry

(cherry picked from commit 35474d997d)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/journal_entry.json
#	erpnext/accounts/doctype/journal_entry/journal_entry.py

* fix: conflicts raised because of cherry pick while backporting

* fix: conflicts

---------

Co-authored-by: Raheel Khan <raheel@frappe.io>
2025-10-14 11:36:15 +05:30
mergify[bot]
b7c2405113 fix(Supplier Quotation Comparison): add a missing translate function (backport #49497) (#50055)
fix(Supplier Quotation Comparison): add a missing translate function (#49497)

* Update supplier_quotation_comparison.py

* refactor: text cleaning

(cherry picked from commit 6cacead726)

Co-authored-by: El-Shafei H. <el.shafei.developer@gmail.com>
2025-10-14 00:36:15 +05:30
rohitwaghchaure
f8a1ef055d Merge pull request #50051 from frappe/mergify/bp/version-15-hotfix/pr-50047
fix: enhance sub-assembly item handling in raw material request calculations (backport #50047)
2025-10-14 00:00:29 +05:30
Mihir Kandoi
664ac3d422 Merge pull request #50052 from frappe/mergify/bp/version-15-hotfix/pr-50025 2025-10-13 20:04:14 +05:30
Mihir Kandoi
5603467cee refactor: move value inline
(cherry picked from commit 1717a7c983)
2025-10-13 14:16:47 +00:00
thomasantony12
e23616f9ea chore: use get_single_value instead of get_cached_doc
(cherry picked from commit fab7f9ee53)
2025-10-13 14:16:47 +00:00
thomasantony12
50266d3b6b fix: Batch ordering based on the method mentioned in settings
(cherry picked from commit 7fa800b874)
2025-10-13 14:16:47 +00:00
Smit Vora
75d14985e0 chore: resolve conflicts 2025-10-13 19:45:04 +05:30
Smit Vora
467fcea728 fix: enhance sub-assembly item handling in raw material request calculations
(cherry picked from commit f912c8419a)

# Conflicts:
#	erpnext/manufacturing/doctype/production_plan/production_plan.py
2025-10-13 14:06:34 +00:00
Diptanil Saha
6a7004e4f7 Merge pull request #50046 from frappe/mergify/bp/version-15-hotfix/pr-49939
fix: preserve address if present (backport #49939)
2025-10-13 16:41:48 +05:30
diptanilsaha
5d1aa4050d chore: resolve conflicts 2025-10-13 16:36:36 +05:30
Khushi Rawat
ce6336d5d6 Merge pull request #50045 from frappe/mergify/bp/version-15-hotfix/pr-50040
perf: add composite indexes to Advance Payment Ledger Entry (backport #50040)
2025-10-13 16:25:52 +05:30
ravibharathi656
aaf470cf5c fix: preserve address if present
(cherry picked from commit 0678638106)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
2025-10-13 10:46:23 +00:00
khushi8112
c4cba78124 fix: revert unrelated manual modified timestamp change
(cherry picked from commit 59bd35c64d)
2025-10-13 10:38:12 +00:00
khushi8112
5652e926d7 perf: add composite indexes to Advance Payment Ledger Entry table
(cherry picked from commit 7fcf277055)
2025-10-13 10:38:12 +00:00
ruthra kumar
f68f7aba0b Merge pull request #50038 from frappe/mergify/bp/version-15-hotfix/pr-50017
fix(deferred revenue): validate service stop date (backport #50017)
2025-10-13 12:54:39 +05:30
ruthra kumar
f36da0543b Merge pull request #50037 from frappe/mergify/bp/version-15-hotfix/pr-50034
fix: set default roles on Role Profiles during reinstallation (backport #50034)
2025-10-13 12:38:01 +05:30
ravibharathi656
557d53a953 fix(deferred revenue): validate service stop date
(cherry picked from commit 58203a89f1)
2025-10-13 07:07:54 +00:00
diptanilsaha
c93fbf3982 fix: set default roles on role_profile during reinstallation
(cherry picked from commit 12c1b8a910)
2025-10-13 06:38:40 +00:00
rohitwaghchaure
d7bf7d0f4d Merge pull request #50026 from frappe/mergify/bp/version-15-hotfix/pr-50024
fix: stock ledger adjustment entry (backport #50024)
2025-10-12 12:12:59 +05:30
Rohit Waghchaure
8020159c14 fix: stock ledger adjustment entry
(cherry picked from commit 8b6e58d02a)
2025-10-12 06:11:44 +00:00
mergify[bot]
1e91c0f5aa Merge pull request #50006 from frappe/mergify/bp/version-15-hotfix/pr-49993
fix: incorrect PR status when using set landed cost based on PI rate (backport #49993)
2025-10-10 11:18:30 +00:00
Diptanil Saha
47af5747bd Merge pull request #50001 from diptanilsaha/backport_49764
feat: Cache employee name in session data on boot (backport #49764)
2025-10-10 12:33:39 +05:30
Khushi Rawat
3e8bf03e80 Merge pull request #50000 from frappe/mergify/bp/version-15-hotfix/pr-49980
fix: fixed asset register showing opening entries (backport #49980)
2025-10-10 12:01:10 +05:30
Khushi Rawat
fe32257450 Merge pull request #49999 from frappe/mergify/bp/version-15-hotfix/pr-49995
feat: add asset name to Asset Depreciations and Balances report (backport #49995)
2025-10-10 12:00:54 +05:30
Diptanil Saha
97b89da7c7 Merge pull request #49764 from elshafei-developer/add-employee-name-to-session-user
feat: Cache employee name in session data on boot
2025-10-10 11:45:38 +05:30
ravibharathi656
1ea6e1db12 fix: fixed asset register showing opening entries
(cherry picked from commit c9d98eb4f0)
2025-10-10 05:44:27 +00:00
Rehan Ansari
0776b300e8 feat: add asset name to Asset Depreciations and Balances report
(cherry picked from commit b4cf6a1fb9)
2025-10-10 05:35:25 +00:00
rohitwaghchaure
c350e9dabd Merge pull request #49994 from frappe/mergify/bp/version-15-hotfix/pr-49991
fix: consider negative qty in batch qty calculation (backport #49991)
2025-10-09 22:45:02 +05:30
Rohit Waghchaure
4370a59183 fix: consider negative qty in batch qty calculation
(cherry picked from commit 912ffc2d64)
2025-10-09 15:23:47 +00:00
rohitwaghchaure
1ab45386a8 Merge pull request #49984 from frappe/mergify/bp/version-15-hotfix/pr-49975
fix: sales return for product bundle items (backport #49975)
2025-10-09 17:38:55 +05:30
rohitwaghchaure
1fbc03c104 chore: fix conflicts 2025-10-09 17:21:03 +05:30
Rohit Waghchaure
6ba55bbee0 test: test case for sales return for product bundle
(cherry picked from commit 1d57bbca11)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/test_delivery_note.py
2025-10-09 09:40:27 +00:00
Rohit Waghchaure
ac46b3d1ca fix: sales return for product bundle items
(cherry picked from commit 13ce7279a8)
2025-10-09 09:40:26 +00:00
rohitwaghchaure
6e55f53cf6 Merge pull request #49974 from frappe/mergify/bp/version-15-hotfix/pr-49973
fix: Reset Raw Materials Table button not working (backport #49973)
2025-10-08 23:45:42 +05:30
Rohit Waghchaure
81ed32ff51 fix: Reset Raw Materials Table button not working
(cherry picked from commit 128e243945)
2025-10-08 17:05:21 +00:00
Khushi Rawat
5d9c245ddd Merge pull request #49956 from frappe/mergify/bp/version-15-hotfix/pr-49954
fix(asset movement): clear custodian if not present (backport #49954)
2025-10-08 21:33:20 +05:30
rohitwaghchaure
fda021430d Merge pull request #49971 from frappe/mergify/bp/version-15-hotfix/pr-49967
fix: batch qty for expired batches (backport #49967)
2025-10-08 19:41:42 +05:30
Rohit Waghchaure
f4816e4960 fix: batch qty for expired batches
(cherry picked from commit ff2faf36a7)
2025-10-08 13:49:28 +00:00
rohitwaghchaure
33f67012b4 Merge pull request #49968 from frappe/mergify/bp/version-15-hotfix/pr-49966
fix: incorrect field valuation_rate (backport #49966)
2025-10-08 19:07:29 +05:30
Rohit Waghchaure
93df11a0cf fix: incorrect field valuation_rate
(cherry picked from commit 630d873214)
2025-10-08 13:15:53 +00:00
Khushi Rawat
38e1ca1362 fix: resolve conflict 2025-10-08 12:53:25 +05:30
ravibharathi656
4ec5b28fd2 fix(asset movement): clear custodian if not present
(cherry picked from commit 323d8eaccd)

# Conflicts:
#	erpnext/assets/doctype/asset_movement/asset_movement.py
2025-10-08 07:05:10 +00:00
51 changed files with 382 additions and 157 deletions

View File

@@ -6,7 +6,7 @@ Feature requests are also a great way to take the product forward. New ideas can
When you are raising an Issue, you should keep a few things in mind. Remember that the developer does not have access to your machine so you must give all the information you can while raising an Issue. If you are suggesting a feature, you should be very clear about what you want.
The Issue list is not the right place to ask a question or start a general discussion. If you want to do that , then the right place is the forum [https://discuss.erpnext.com](https://discuss.erpnext.com).
The Issue list is not the right place to ask a question or start a general discussion. If you want to do that , then the right place is the forum [https://discuss.frappe.io](https://discuss.frappe.io/c/erpnext/6).
### Reply and Closing Policy

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ assignees: ''
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
- For questions and general support, checkout the manual https://erpnext.com/docs/user/manual/en or use https://discuss.erpnext.com
- For questions and general support, checkout the manual https://docs.erpnext.com or use https://discuss.frappe.io/c/erpnext/6
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
the original discussion.
3. When making a feature request, make sure to be as verbose as possible. The better you convey your message, the greater the drive to make it happen.
@@ -21,7 +21,7 @@ Please keep in mind that we get many many requests and we can't possibly work on
If you're in urgent need to a feature, please try the following channels to get paid developments done quickly:
1. Certified ERPNext partners: https://erpnext.com/partners
2. Developer community on ERPNext forums: https://discuss.erpnext.com/c/developers/5
2. Developer community on ERPNext forums: https://discuss.frappe.io/c/framework/5
3. Telegram group for ERPNext/Frappe development work: https://t.me/erpnext_opps
-->

View File

@@ -64,7 +64,7 @@ New passwords will be created for the ERPNext "Administrator" user, the MariaDB
1. [Frappe School](https://school.frappe.io) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext.
3. [Discussion Forum](https://discuss.erpnext.com/) - Engage with community of ERPNext users and service providers.
3. [Discussion Forum](https://discuss.frappe.io/c/erpnext/6) - Engage with community of ERPNext users and service providers.
4. [Telegram Group](https://erpnext_public.t.me) - Get instant help from huge community of users.

View File

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

View File

@@ -46,7 +46,8 @@ def validate_service_stop_date(doc):
if (
old_stop_dates
and old_stop_dates.get(item.name)
and item.service_stop_date != old_stop_dates.get(item.name)
and item.service_stop_date
and getdate(item.service_stop_date) != getdate(old_stop_dates.get(item.name))
):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))

View File

@@ -82,7 +82,7 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-07-29 11:37:42.678556",
"modified": "2025-10-13 15:11:58.300836",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Payment Ledger Entry",

View File

@@ -34,3 +34,15 @@ class AdvancePaymentLedgerEntry(Document):
and not frappe.flags.is_reverse_depr_entry
):
update_voucher_outstanding(self.against_voucher_type, self.against_voucher_no, None, None, None)
def on_doctype_update():
frappe.db.add_index(
"Advance Payment Ledger Entry",
["against_voucher_type", "against_voucher_no"],
)
frappe.db.add_index(
"Advance Payment Ledger Entry",
["voucher_type", "voucher_no"],
)

View File

@@ -131,8 +131,8 @@ class GLEntry(Document):
if not self.is_cancelled and not (self.party_type and self.party):
account_type = frappe.get_cached_value("Account", self.account, "account_type")
# skipping validation for payroll entry creation in case party is not required
if not frappe.flags.party_not_required_for_receivable_payable:
if not frappe.flags.party_not_required: # skipping validation if party is not required
if account_type == "Receivable":
frappe.throw(
_("{0} {1}: Customer is required against Receivable account {2}").format(

View File

@@ -59,6 +59,7 @@
"addtional_info",
"mode_of_payment",
"payment_order",
"party_not_required",
"column_break3",
"is_opening",
"stock_entry",
@@ -543,6 +544,14 @@
"label": "Is System Generated",
"no_copy": 1,
"read_only": 1
},
{
"default": "0",
"fieldname": "party_not_required",
"fieldtype": "Check",
"hidden": 1,
"label": "Party Not Required",
"no_copy": 1
}
],
"icon": "fa fa-file-text",
@@ -557,7 +566,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2024-07-18 15:32:29.413598",
"modified": "2025-09-29 13:05:46.982277",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -72,6 +72,7 @@ class JournalEntry(AccountsController):
multi_currency: DF.Check
naming_series: DF.Literal["ACC-JV-.YYYY.-"]
paid_loan: DF.Data | None
party_not_required: DF.Check
pay_to_recd_from: DF.Data | None
payment_order: DF.Link | None
posting_date: DF.Date
@@ -543,10 +544,10 @@ class JournalEntry(AccountsController):
for d in self.get("accounts"):
account_type = frappe.get_cached_value("Account", d.account, "account_type")
# skipping validation for payroll entry creation
skip_validation = frappe.flags.party_not_required_for_receivable_payable
if account_type in ["Receivable", "Payable"]:
if not (d.party_type and d.party) and not skip_validation:
if (
not (d.party_type and d.party) and not self.party_not_required
): # skipping validation if party_not_required is passed via payroll entry
frappe.throw(
_(
"Row {0}: Party Type and Party is required for Receivable / Payable account {1}"
@@ -1139,6 +1140,11 @@ class JournalEntry(AccountsController):
}
)
# set flag to skip party validation
account_type = frappe.get_cached_value("Account", d.account, "account_type")
if account_type in ["Receivable", "Payable"] and self.party_not_required:
frappe.flags.party_not_required = True
gl_map.append(
self.get_gl_dict(
row,
@@ -1166,6 +1172,7 @@ class JournalEntry(AccountsController):
merge_entries=merge_entries,
update_outstanding=update_outstanding,
)
frappe.flags.party_not_required = False
if cancel:
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))

View File

@@ -286,7 +286,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-07-25 04:45:28.117715",
"modified": "2025-09-29 13:01:48.916517",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -2640,6 +2640,38 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
@change_settings(
"Buying Settings", {"maintain_same_rate": 0, "set_landed_cost_based_on_purchase_invoice_rate": 1}
)
def test_pr_status_rate_adjusted_from_pi(self):
pr = make_purchase_receipt(qty=5, rate=100)
pi = create_purchase_invoice_from_receipt(pr.name)
pi.submit()
pr.reload()
# Inital check
self.assertEqual(pr.status, "Completed")
pi.reload()
pi.cancel()
pi = create_purchase_invoice_from_receipt(pr.name)
pi.items[0].rate = 80
pi.submit()
pr.reload()
# Test 1 : Adjustment amount is negative
self.assertEqual(pr.status, "Completed")
pi.reload()
pi.cancel()
pi = create_purchase_invoice_from_receipt(pr.name)
pi.items[0].rate = 120
pi.submit()
pr.reload()
# Test 2 : Adjustment amount is positive
self.assertEqual(pr.status, "Completed")
def test_opening_invoice_rounding_adjustment_validation(self):
pi = make_purchase_invoice(do_not_save=1)
pi.items[0].rate = 99.98

View File

@@ -912,7 +912,8 @@
"label": "Rejected Serial and Batch Bundle",
"no_copy": 1,
"options": "Serial and Batch Bundle",
"print_hide": 1
"print_hide": 1,
"search_index": 1
},
{
"fieldname": "wip_composite_asset",
@@ -983,7 +984,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-03-12 16:33:13.453290",
"modified": "2025-10-14 13:01:54.441511",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
@@ -993,4 +994,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -798,6 +798,15 @@ frappe.ui.form.on("Sales Invoice", {
},
};
};
frm.set_query("sales_person", "sales_team", function () {
return {
filters: {
is_group: 0,
enabled: 1,
},
};
});
},
onload: function (frm) {
frm.redemption_conversion_factor = null;

View File

@@ -2411,6 +2411,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
target.purchase_order = source.purchase_order
target.po_detail = source.purchase_order_item
if (source.get("serial_no") or source.get("batch_no")) and not source.get("serial_and_batch_bundle"):
target.use_serial_batch_fields = 1
item_field_map = {
"doctype": target_doctype + " Item",
"field_no_map": ["income_account", "expense_account", "cost_center", "warehouse"],

View File

@@ -354,7 +354,7 @@ def get_asset_details_for_grouped_by_category(filters):
# nosemgrep
return frappe.db.sql(
f"""
SELECT a.name,
SELECT a.name, a.asset_name,
ifnull(sum(case when a.purchase_date < %(from_date)s then
case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then
a.gross_purchase_amount
@@ -583,6 +583,14 @@ def get_columns(filters):
"width": 120,
}
)
columns.append(
{
"label": _("Asset Name"),
"fieldname": "asset_name",
"fieldtype": "Data",
"width": 140,
}
)
columns += [
{

View File

@@ -507,7 +507,8 @@ def depreciate_asset(asset_doc, date, notes):
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
asset_doc.reload()
cancel_depreciation_entries(asset_doc, date)
if not frappe.flags.is_composite_component:
cancel_depreciation_entries(asset_doc, date)
@erpnext.allow_regional

View File

@@ -492,14 +492,18 @@ class AssetCapitalization(StockController):
asset = frappe.get_doc("Asset", item.asset)
if asset.calculate_depreciation:
notes = _(
"This schedule was created when Asset {0} was consumed through Asset Capitalization {1}."
).format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(self.doctype, self.get("name")),
)
depreciate_asset(asset, self.posting_date, notes)
asset.reload()
frappe.flags.is_composite_component = True
try:
notes = _(
"This schedule was created when Asset {0} was consumed through Asset Capitalization {1}."
).format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(self.doctype, self.get("name")),
)
depreciate_asset(asset, self.posting_date, notes)
asset.reload()
finally:
frappe.flags.is_composite_component = False
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset,

View File

@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_link_to_form
from frappe.utils import cstr, get_link_to_form
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
@@ -142,18 +142,10 @@ class AssetMovement(Document):
def update_asset_location_and_custodian(self, asset_id, location, employee):
asset = frappe.get_doc("Asset", asset_id)
updates = {}
if employee and employee != asset.custodian:
updates["custodian"] = employee
elif not employee and asset.custodian:
updates["custodian"] = ""
if cstr(employee) != asset.custodian:
frappe.db.set_value("Asset", asset_id, "custodian", cstr(employee))
if location and location != asset.location:
updates["location"] = location
if updates:
frappe.db.set_value("Asset", asset_id, updates)
frappe.db.set_value("Asset", asset_id, "location", location)
def log_asset_activity(self, asset_id, location, employee):
if location and employee:

View File

@@ -319,6 +319,7 @@ def get_asset_value_adjustment_map(filters, finance_book):
.select(asset.name.as_("asset"), Sum(gle.debit - gle.credit).as_("adjustment_amount"))
.where(gle.account == aca.fixed_asset_account)
.where(gle.is_cancelled == 0)
.where(gle.is_opening == "No")
.where(company.name == filters.company)
.where(asset.docstatus == 1)
)

View File

@@ -284,15 +284,15 @@ def get_columns(filters):
def get_message():
return """<span class="indicator">
Valid till : &nbsp;&nbsp;
return f"""<span class="indicator">
{_("Valid Till")}:&nbsp;&nbsp;
</span>
<span class="indicator orange">
Expires in a week or less
{_("Expires in a week or less")}
</span>
&nbsp;&nbsp;
<span class="indicator red">
Expires today / Already Expired
{_("Expires today or already expired")}
</span>"""

View File

@@ -253,6 +253,14 @@ class StockController(AccountsController):
"do_not_submit": True if not via_landed_cost_voucher else False,
}
if self.is_internal_transfer() and row.get("from_warehouse") and not self.is_return:
self.update_bundle_details(bundle_details, table_name, row)
bundle_details["type_of_transaction"] = "Outward"
bundle_details["warehouse"] = row.get("from_warehouse")
bundle_details["qty"] = row.get("stock_qty") or row.get("qty")
self.create_serial_batch_bundle(bundle_details, row)
continue
if row.get("qty") or row.get("consumed_qty") or row.get("stock_qty"):
self.update_bundle_details(bundle_details, table_name, row, parent_details=parent_details)
self.create_serial_batch_bundle(bundle_details, row)

View File

@@ -637,7 +637,8 @@ class SubcontractingController(StockController):
if use_serial_batch_fields:
rm_obj.use_serial_batch_fields = 1
self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
if not self.flags.get("reset_raw_materials"):
self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
if self.doctype == "Subcontracting Receipt":
if not use_serial_batch_fields:

View File

@@ -24,6 +24,7 @@ frappe.ui.form.on("Production Plan", {
query: "erpnext.manufacturing.doctype.production_plan.production_plan.sales_order_query",
filters: {
company: frm.doc.company,
item_code: frm.doc.item_code,
},
};
});
@@ -105,6 +106,8 @@ frappe.ui.form.on("Production Plan", {
__("View")
);
let has_create_buttons = false;
if (frm.doc.status !== "Completed") {
if (frm.doc.status === "Closed") {
frm.add_custom_button(
@@ -134,6 +137,7 @@ frappe.ui.form.on("Production Plan", {
},
__("Create")
);
has_create_buttons = true;
}
if (
@@ -148,12 +152,13 @@ frappe.ui.form.on("Production Plan", {
},
__("Create")
);
has_create_buttons = true;
}
}
}
if (frm.doc.status !== "Closed") {
frm.page.set_inner_btn_group_as_primary(__("Create"));
if (has_create_buttons && frm.doc.status !== "Closed") {
frm.page.set_inner_btn_group_as_primary(__("Create"));
}
}
frm.trigger("material_requirement");

View File

@@ -1543,6 +1543,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
include_safety_stock = doc.get("include_safety_stock")
so_item_details = frappe._dict()
existing_sub_assembly_items = set()
sub_assembly_items = defaultdict(int)
if doc.get("skip_available_sub_assembly_item") and doc.get("sub_assembly_items"):
@@ -1576,7 +1577,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
item_details = {}
if doc.get("sub_assembly_items"):
item_details = get_raw_materials_of_sub_assembly_items(
so_item_details[doc.get("sales_order")].keys() if so_item_details else [],
existing_sub_assembly_items,
item_details,
company,
bom_no,
@@ -1839,7 +1840,7 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
frappe.qb.from_(table)
.inner_join(child)
.on(table.name == child.parent)
.select(Sum(child.required_bom_qty))
.select(Sum(child.quantity * child.conversion_factor))
.where(
(table.docstatus == 1)
& (child.item_code == item_code)
@@ -1955,6 +1956,7 @@ def get_raw_materials_of_sub_assembly_items(
sub_assembly_items,
planned_qty=planned_qty,
)
existing_sub_assembly_items.add(item.item_code)
else:
if not item.conversion_factor and item.purchase_uom:
item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom)
@@ -1992,6 +1994,9 @@ def sales_order_query(doctype=None, txt=None, searchfield=None, start=None, page
if filters.get("sales_orders"):
query = query.where(so_table.name.isin(filters.get("sales_orders")))
if filters.get("item_code"):
query = query.where(table.item_code == filters.get("item_code"))
if txt:
query = query.where(table.parent.like(f"%{txt}%"))

View File

@@ -1637,11 +1637,17 @@ class TestProductionPlan(FrappeTestCase):
def test_calculation_of_sub_assembly_items(self):
make_item("Sub Assembly Item ", properties={"is_stock_item": 1})
make_item("Sub Assembly Item 2", properties={"is_stock_item": 1})
make_item("RM Item 1", properties={"is_stock_item": 1})
make_item("RM Item 2", properties={"is_stock_item": 1})
make_item("_Test FG Item 3", properties={"is_stock_item": 1})
make_item("_Test FG Item 4", properties={"is_stock_item": 1})
make_bom(item="Sub Assembly Item", raw_materials=["RM Item 1", "RM Item 2"])
make_bom(item="Sub Assembly Item 2", raw_materials=["RM Item 2"])
make_bom(item="_Test FG Item", raw_materials=["Sub Assembly Item", "RM Item 1"])
make_bom(item="_Test FG Item 2", raw_materials=["Sub Assembly Item"])
make_bom(item="_Test FG Item 3", raw_materials=["RM Item 1"])
make_bom(item="_Test FG Item 4", raw_materials=["Sub Assembly Item 2"])
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -1677,12 +1683,39 @@ class TestProductionPlan(FrappeTestCase):
"warehouse": "_Test Warehouse - _TC",
},
)
# Assembly item with similar RM item
plan.append(
"po_items",
{
"use_multi_level_bom": 1,
"item_code": "_Test FG Item 3",
"bom_no": frappe.db.get_value("Item", "_Test FG Item 3", "default_bom"),
"planned_qty": 10,
"planned_start_date": now_datetime(),
"stock_uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
},
)
# Sub-assembly item with similar RM item
plan.append(
"po_items",
{
"use_multi_level_bom": 1,
"item_code": "_Test FG Item 4",
"bom_no": frappe.db.get_value("Item", "_Test FG Item 4", "default_bom"),
"planned_qty": 10,
"planned_start_date": now_datetime(),
"stock_uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
},
)
plan.save()
plan.get_sub_assembly_items()
self.assertEqual(plan.sub_assembly_items[0].qty, 20)
self.assertEqual(plan.sub_assembly_items[1].qty, 50)
self.assertEqual(plan.sub_assembly_items[0].qty, 20) # Sub Assembly For FG 1
self.assertEqual(plan.sub_assembly_items[1].qty, 50) # Sub Assembly For FG 2
self.assertEqual(plan.sub_assembly_items[2].qty, 10) # Sub Assembly For FG 4
from erpnext.manufacturing.doctype.production_plan.production_plan import (
get_items_for_material_requests,
@@ -1690,8 +1723,11 @@ class TestProductionPlan(FrappeTestCase):
mr_items = get_items_for_material_requests(plan.as_dict())
self.assertEqual(mr_items[0].get("quantity"), 80)
self.assertEqual(mr_items[1].get("quantity"), 70)
# RM Item 1 (FG1 (100 + 100) + FG2 (50) + FG3 (10) - 90 in stock - 80 sub assembly stock)
self.assertEqual(mr_items[0].get("quantity"), 90)
# RM Item 2 (FG1 (100) + FG2 (50) + FG4 (10) - 80 sub assembly stock)
self.assertEqual(mr_items[1].get("quantity"), 80)
def test_production_plan_for_partial_sub_assembly_items(self):
from erpnext.controllers.status_updater import OverAllowanceError

View File

@@ -113,6 +113,13 @@ class ProductionPlanReport:
self.orders = query.run(as_dict=True)
def get_raw_materials(self):
"""Retrieve raw materials and source warehouses for production orders.
This method collects BOM or Work Order items depending on the selected
filter and updates `self.raw_materials_dict`, `self.warehouses`,
and `self.item_codes` accordingly.
"""
if not self.orders:
return
self.warehouses = [d.warehouse for d in self.orders]
@@ -135,7 +142,7 @@ class ProductionPlanReport:
)
or []
)
self.warehouses.extend([d.source_warehouse for d in raw_materials])
self.warehouses.extend([d.warehouse for d in raw_materials])
else:
bom_nos = []

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

@@ -171,13 +171,15 @@ erpnext.buying = {
shipping_address: this.frm.doc.shipping_address
},
callback: (r) => {
this.frm.set_value("billing_address", r.message.primary_address || "");
if (!this.frm.doc.billing_address)
this.frm.set_value("billing_address", r.message.primary_address || "");
if (!frappe.meta.has_field(this.frm.doc.doctype, "shipping_address")) return;
this.frm.set_value(
"shipping_address",
r.message.shipping_address || this.frm.doc.shipping_address || ""
);
if (
!frappe.meta.has_field(this.frm.doc.doctype, "shipping_address") ||
this.frm.doc.shipping_address
)
return;
this.frm.set_value("shipping_address", r.message.shipping_address || "");
},
});
erpnext.utils.set_letter_head(this.frm)

View File

@@ -1022,19 +1022,20 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
} else {
set_pricing();
}
};
}
if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doctype)) {
let is_drop_ship = me.frm.doc.items.some(item => item.delivered_by_supplier);
if (!is_drop_ship) {
erpnext.utils.get_shipping_address(this.frm, function() {
set_party_account(set_pricing);
});
}
if (
frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doctype) &&
!this.frm.doc.shipping_address
) {
let is_drop_ship = me.frm.doc.items.some((item) => item.delivered_by_supplier);
if (!is_drop_ship) {
erpnext.utils.get_shipping_address(this.frm, function() {
set_party_account(set_pricing);
});
}
} else {
set_party_account(set_pricing);
}

View File

@@ -109,6 +109,7 @@ erpnext.sales_common = {
);
this.toggle_editable_price_list_rate();
this.change_warehouse_labels_for_return();
}
company() {
@@ -500,6 +501,33 @@ erpnext.sales_common = {
this.frm.set_value("discount_amount", 0);
this.frm.set_value("additional_discount_percentage", 0);
}
is_return() {
let reset = !this.frm.doc.is_return;
this.change_warehouse_labels_for_return(reset);
}
change_warehouse_labels_for_return(reset) {
// swap source and target warehouse labels for return
let source_warehouse_label = __("Source Warehouse");
let target_warehouse_label = __("Set Target Warehouse");
if (this.frm.doc.doctype == "Delivery Note") {
source_warehouse_label = __("Set Source Warehouse");
}
if (reset) {
// reset to original labels
this.frm.set_df_property("set_warehouse", "label", source_warehouse_label);
this.frm.set_df_property("set_target_warehouse", "label", target_warehouse_label);
return;
}
if (this.frm.doc.is_return) {
this.frm.set_df_property("set_warehouse", "label", target_warehouse_label);
this.frm.set_df_property("set_target_warehouse", "label", source_warehouse_label);
}
}
};
},
};

View File

@@ -457,7 +457,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
(["Purchase Receipt", "Purchase Invoice"].includes(this.frm.doc.doctype) &&
!this.frm.doc.is_return) ||
(this.frm.doc.doctype === "Stock Entry" &&
this.frm.doc.purpose === "Material Receipt")
(this.frm.doc.purpose === "Material Receipt" ||
(this.frm.doc.purpose === "Manufacture" && this.item.is_finished_item)))
) {
is_inward = true;
}

View File

@@ -44,6 +44,15 @@ frappe.ui.form.on("Sales Order", {
};
});
frm.set_query("sales_person", "sales_team", function () {
return {
filters: {
is_group: 0,
enabled: 1,
},
};
});
frm.set_df_property("packed_items", "cannot_add_rows", true);
frm.set_df_property("packed_items", "cannot_delete_rows", true);
},

View File

@@ -262,6 +262,20 @@ def update_roles():
def create_default_role_profiles():
for role_profile_name, roles in DEFAULT_ROLE_PROFILES.items():
if frappe.db.exists("Role Profile", role_profile_name):
role_profile = frappe.get_doc("Role Profile", role_profile_name)
existing_roles = [row.role for row in role_profile.roles]
role_profile.roles = [row for row in role_profile.roles if row.role in roles]
for role in roles:
if role not in existing_roles:
role_profile.append("roles", {"role": role})
role_profile.save(ignore_permissions=True)
continue
role_profile = frappe.new_doc("Role Profile")
role_profile.role_profile = role_profile_name
for role in roles:

View File

@@ -74,6 +74,8 @@ def update_page_info(bootinfo):
def bootinfo(bootinfo):
if bootinfo.get("user") and bootinfo["user"].get("name"):
bootinfo["user"]["employee"] = ""
frappe.session.data.employee = ""
employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name")
if employee:
bootinfo["user"]["employee"] = employee
frappe.session.data.employee = employee

View File

@@ -78,6 +78,7 @@ class DeprecatedBatchNoValuation:
for ledger in entries:
self.stock_value_differece[ledger.batch_no] += flt(ledger.batch_value)
self.available_qty[ledger.batch_no] += flt(ledger.batch_qty)
self.total_qty[ledger.batch_no] += flt(ledger.batch_qty)
@deprecated
def get_sle_for_batches(self):
@@ -230,6 +231,7 @@ class DeprecatedBatchNoValuation:
batch_data = query.run(as_dict=True)
for d in batch_data:
self.available_qty[d.batch_no] += flt(d.batch_qty)
self.total_qty[d.batch_no] += flt(d.batch_qty)
for d in batch_data:
if self.available_qty.get(d.batch_no):
@@ -330,6 +332,7 @@ class DeprecatedBatchNoValuation:
batch_data = query.run(as_dict=True)
for d in batch_data:
self.available_qty[d.batch_no] += flt(d.batch_qty)
self.total_qty[d.batch_no] += flt(d.batch_qty)
if not self.last_sle:
return

View File

@@ -158,7 +158,9 @@ class Batch(Document):
@frappe.whitelist()
def recalculate_batch_qty(self):
batches = get_batch_qty(batch_no=self.name, item_code=self.item, for_stock_levels=True)
batches = get_batch_qty(
batch_no=self.name, item_code=self.item, for_stock_levels=True, consider_negative_batches=True
)
batch_qty = 0.0
if batches:
for row in batches:
@@ -260,6 +262,7 @@ def get_batch_qty(
"posting_date": posting_date,
"posting_time": posting_time,
"batch_no": batch_no,
"based_on": frappe.get_single_value("Stock Settings", "pick_serial_and_batch_based_on"),
"ignore_voucher_nos": ignore_voucher_nos,
"for_stock_levels": for_stock_levels,
"consider_negative_batches": consider_negative_batches,

View File

@@ -334,6 +334,7 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
if (
doc.docstatus == 1 &&
!doc.is_return &&
doc.per_returned != 100 &&
doc.status != "Closed" &&
flt(doc.per_billed) < 100 &&
frappe.model.can_create("Sales Invoice")

View File

@@ -10,6 +10,8 @@ from frappe.contacts.doctype.address.address import get_company_address
from frappe.desk.notifications import clear_doctype_notifications
from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values
from frappe.query_builder import DocType
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import cint, flt
from erpnext.accounts.party import get_due_date
@@ -790,35 +792,39 @@ def get_list_context(context=None):
def get_invoiced_qty_map(delivery_note):
"""returns a map: {dn_detail: invoiced_qty}"""
invoiced_qty_map = {}
sii = DocType("Sales Invoice Item")
for dn_detail, qty in frappe.db.sql(
"""select dn_detail, qty from `tabSales Invoice Item`
where delivery_note=%s and docstatus=1""",
delivery_note,
):
if not invoiced_qty_map.get(dn_detail):
invoiced_qty_map[dn_detail] = 0
invoiced_qty_map[dn_detail] += qty
invoiced_qty_map = frappe._dict(
(
frappe.qb.from_(sii)
.select(sii.dn_detail, Sum(sii.qty).as_("qty"))
.where((sii.delivery_note == delivery_note) & (sii.docstatus == 1))
.groupby(sii.dn_detail)
).run()
)
return invoiced_qty_map
def get_returned_qty_map(delivery_note):
"""returns a map: {so_detail: returned_qty}"""
dn = DocType("Delivery Note")
dni = DocType("Delivery Note Item")
returned_qty_map = frappe._dict(
frappe.db.sql(
"""select dn_item.dn_detail, sum(abs(dn_item.qty)) as qty
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
where dn.name = dn_item.parent
and dn.docstatus = 1
and dn.is_return = 1
and dn.return_against = %s
and dn_item.qty <= 0
group by dn_item.item_code
""",
delivery_note,
)
(
frappe.qb.from_(dni)
.join(dn)
.on(dn.name == dni.parent)
.select(dni.dn_detail, Sum(Abs(dni.qty)).as_("qty"))
.where(
(dn.docstatus == 1)
& (dn.is_return == 1)
& (dn.return_against == delivery_note)
& (dni.qty <= 0)
)
.groupby(dni.dn_detail)
).run()
)
return returned_qty_map
@@ -1304,6 +1310,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if source.get("use_serial_batch_fields"):
target.set("use_serial_batch_fields", 1)
if (source.get("serial_no") or source.get("batch_no")) and not source.get("serial_and_batch_bundle"):
target.set("use_serial_batch_fields", 1)
doclist = get_mapped_doc(
doctype,
source_name,

View File

@@ -1115,7 +1115,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
buying_settings = frappe.get_single("Buying Settings")
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
total_amount, total_billed_amount = 0, 0
total_amount, total_billed_amount, pi_landed_cost_amount = 0, 0, 0
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
if adjust_incoming_rate:
@@ -1155,6 +1155,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
) * item.qty
adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount"))
pi_landed_cost_amount += adjusted_amt
item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
elif amount and item.billed_amt > amount:
per_over_billed = (flt(item.billed_amt / amount, 2) * 100) - 100
@@ -1165,6 +1166,9 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
)
)
if pi_landed_cost_amount < 0:
total_billed_amount += abs(pi_landed_cost_amount)
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
pr_doc.db_set("per_billed", percent_billed)

View File

@@ -778,7 +778,8 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Material Request Item",
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "expense_account",
@@ -1038,7 +1039,8 @@
"fieldtype": "Link",
"label": "Rejected Serial and Batch Bundle",
"no_copy": 1,
"options": "Serial and Batch Bundle"
"options": "Serial and Batch Bundle",
"search_index": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0",
@@ -1147,7 +1149,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-03-12 17:10:43.780622",
"modified": "2025-10-14 12:59:20.384056",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
@@ -1158,4 +1160,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -294,7 +294,7 @@ class SerialandBatchBundle(Document):
}
)
if self.returned_against and self.docstatus == 1:
if (self.returned_against or self.voucher_type == "Stock Reconciliation") and self.docstatus == 1:
kwargs["ignore_voucher_detail_no"] = self.voucher_detail_no
if self.docstatus == 1:
@@ -518,12 +518,15 @@ class SerialandBatchBundle(Document):
else:
d.incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(d.batch_no)))
available_qty = flt(sn_obj.available_qty.get(d.batch_no), d.precision("qty"))
if self.docstatus == 1:
available_qty += flt(d.qty, d.precision("qty"))
precision = d.precision("qty")
for field in ["available_qty", "total_qty"]:
value = getattr(sn_obj, field)
available_qty = flt(value.get(d.batch_no), precision)
if self.docstatus == 1:
available_qty += flt(d.qty, precision)
if not allow_negative_stock:
self.validate_negative_batch(d.batch_no, available_qty)
if not allow_negative_stock:
self.validate_negative_batch(d.batch_no, available_qty)
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
@@ -2677,7 +2680,10 @@ def get_stock_ledgers_for_serial_nos(kwargs):
else:
query = query.where(stock_ledger_entry[field] == kwargs.get(field))
if kwargs.voucher_no:
if kwargs.ignore_voucher_detail_no:
query = query.where(stock_ledger_entry.voucher_detail_no != kwargs.ignore_voucher_detail_no)
elif kwargs.voucher_no:
query = query.where(stock_ledger_entry.voucher_no != kwargs.voucher_no)
return query.run(as_dict=True)

View File

@@ -1323,18 +1323,9 @@ class TestStockEntry(FrappeTestCase):
posting_date="2021-07-02", # Illegal SE
purpose="Material Transfer",
),
dict(
item_code=item_code,
qty=2,
from_warehouse=warehouse_names[0],
to_warehouse=warehouse_names[1],
batch_no=batch_no,
posting_date="2021-07-02", # Illegal SE
purpose="Material Transfer",
),
]
self.assertRaises(frappe.ValidationError, create_stock_entries, sequence_of_entries)
self.assertRaises(NegativeStockError, create_stock_entries, sequence_of_entries)
@change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_future_negative_sle_batch(self):

View File

@@ -188,6 +188,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Basic Rate (as per Stock UOM)",
"non_negative": 1,
"oldfieldname": "incoming_rate",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
@@ -446,7 +447,8 @@
"no_copy": 1,
"options": "Stock Entry",
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "ste_detail",
@@ -454,7 +456,8 @@
"label": "Stock Entry Child",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "column_break_51",
@@ -613,7 +616,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-03-26 21:01:58.544797",
"modified": "2025-10-14 15:10:38.373099",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",

View File

@@ -589,6 +589,10 @@ class StockReconciliation(StockController):
if row.get(field):
key.append(row.get(field))
for dimension in get_inventory_dimensions():
if row.get(dimension.get("fieldname")):
key.append(row.get(dimension.get("fieldname")))
if key in item_warehouse_combinations:
self.validation_messages.append(
_get_msg(row_num, _("Same item and warehouse combination already entered."))

View File

@@ -36,17 +36,6 @@
"show_barcode_field",
"clean_description_html",
"allow_internal_transfer_at_arms_length_price",
"quality_inspection_settings_section",
"action_if_quality_inspection_is_not_submitted",
"column_break_23",
"action_if_quality_inspection_is_rejected",
"stock_reservation_tab",
"enable_stock_reservation",
"column_break_rx3e",
"allow_partial_reservation",
"auto_reserve_stock_for_sales_order_on_purchase",
"serial_and_batch_reservation_section",
"auto_reserve_serial_and_batch",
"serial_and_batch_item_settings_tab",
"section_break_7",
"allow_existing_serial_no",
@@ -548,8 +537,8 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-05-06 02:39:24.284587",
"modified_by": "Administrator",
"modified": "2025-10-17 18:32:35.829395",
"modified_by": "hello@aerele.in",
"module": "Stock",
"name": "Stock Settings",
"owner": "Administrator",
@@ -569,8 +558,9 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -3,6 +3,7 @@ from collections import defaultdict
import frappe
from frappe import _, bold
from frappe.model.naming import make_autoname
from frappe.query_builder import Case
from frappe.query_builder.functions import CombineDatetime, Sum, Timestamp
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, now, nowtime, today
from pypika import Order
@@ -708,6 +709,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
for key, value in kwargs.items():
setattr(self, key, value)
self.total_qty = defaultdict(float)
self.stock_queue = []
self.batch_nos = self.get_batch_nos()
self.prepare_batches()
@@ -729,6 +731,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
for ledger in entries:
self.stock_value_differece[ledger.batch_no] += flt(ledger.incoming_rate)
self.available_qty[ledger.batch_no] += flt(ledger.qty)
self.total_qty[ledger.batch_no] += flt(ledger.total_qty)
self.calculate_avg_rate_from_deprecarated_ledgers()
self.calculate_avg_rate_for_non_batchwise_valuation()
@@ -762,13 +765,16 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
.on(parent.name == child.parent)
.select(
child.batch_no,
Sum(child.stock_value_difference).as_("incoming_rate"),
Sum(child.qty).as_("qty"),
Sum(Case().when(timestamp_condition, child.stock_value_difference).else_(0)).as_(
"incoming_rate"
),
Sum(Case().when(timestamp_condition, child.qty).else_(0)).as_("qty"),
Sum(child.qty).as_("total_qty"),
)
.where(
(child.batch_no.isin(self.batchwise_valuation_batches))
& (parent.warehouse == self.sle.warehouse)
(parent.warehouse == self.sle.warehouse)
& (parent.item_code == self.sle.item_code)
& (child.batch_no.isin(self.batchwise_valuation_batches))
& (parent.docstatus == 1)
& (parent.is_cancelled == 0)
& (parent.type_of_transaction.isin(["Inward", "Outward"]))
@@ -784,8 +790,6 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
query = query.where(parent.voucher_no != self.sle.voucher_no)
query = query.where(parent.voucher_type != "Pick List")
if timestamp_condition:
query = query.where(timestamp_condition)
return query.run(as_dict=True)
@@ -1385,12 +1389,12 @@ def get_batch_current_qty(batch):
def throw_negative_batch_validation(batch_no, qty):
frappe.msgprint(
# This validation is important for backdated stock transactions with batch items
frappe.throw(
_(
"The Batch {0} has negative batch quantity {1}. To fix this, go to the batch and click on Recalculate Batch Qty. If the issue still persists, create an inward entry."
).format(bold(get_link_to_form("Batch", batch_no)), bold(qty)),
title=_("Warning!"),
indicator="orange",
title=_("Negative Stock Error"),
)

View File

@@ -732,6 +732,10 @@ class update_entries_after:
elif dependant_sle.voucher_type == "Stock Entry" and is_transfer_stock_entry(
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
@@ -888,9 +892,8 @@ class update_entries_after:
sle.stock_value = self.wh_data.stock_value
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
if not sle.is_adjustment_entry:
sle.stock_value_difference = stock_value_difference
elif sle.is_adjustment_entry and not self.args.get("sle_id"):
sle.stock_value_difference = stock_value_difference
if sle.is_adjustment_entry and flt(sle.qty_after_transaction, self.flt_precision) == 0:
sle.stock_value_difference = (
get_stock_value_difference(
sle.item_code, sle.warehouse, sle.posting_date, sle.posting_time, sle.voucher_no

View File

@@ -195,6 +195,7 @@ class SubcontractingReceipt(SubcontractingController):
@frappe.whitelist()
def reset_raw_materials(self):
self.supplied_items = []
self.flags.reset_raw_materials = True
self.create_raw_materials_supplied()
def validate_closed_subcontracting_order(self):