Compare commits

..

328 Commits

Author SHA1 Message Date
Frappe PR Bot
4ef07f97c9 chore(release): Bumped to Version 15.85.1
## [15.85.1](https://github.com/frappe/erpnext/compare/v15.85.0...v15.85.1) (2025-10-30)

### Bug Fixes

* **pos:** order pos invoices by timestamp ([8bd1e8f](8bd1e8ff74))
2025-10-30 15:42:14 +00:00
rohitwaghchaure
670579bde3 Merge pull request #50278 from frappe/mergify/bp/version-15/pr-50277
fix(pos): order pos invoices by timestamp (backport #50277)
2025-10-30 21:10:53 +05:30
Kavin
8bd1e8ff74 fix(pos): order pos invoices by timestamp
(cherry picked from commit 12903b11ed)
2025-10-30 13:20:53 +00:00
Frappe PR Bot
49f66a8a51 chore(release): Bumped to Version 15.85.0
# [15.85.0](https://github.com/frappe/erpnext/compare/v15.84.0...v15.85.0) (2025-10-28)

### Bug Fixes

* **accounts:** update payment mode account retrieval to use namespaced function ([2300660](23006601b2))
* add message for missing purchase orders in production plan ([5798409](5798409f69))
* **asset depreciations and balances:** showing opening entries ([81f19b9](81f19b950d))
* avoid group columns mutation ([158e1b2](158e1b28a9))
* **Bank Transaction:** make transaction ID non-unique ([b52b04a](b52b04a10c))
* fiscal year overlap validation for company-specific years ([482a796](482a796212))
* get valuation rate based of previous SLEs for material receipt ([e9f4a34](e9f4a34d8d))
* **gross profit:** remove customer name from columns ([a767253](a7672530f4))
* **journal-entry:** allow copy account currency when duplicating JV ([1b227b8](1b227b8b4f))
* optimized the slow query to get the batchwise available qty ([85bf936](85bf9366b0))
* Pass uom field name to update existing item qty ([93b2786](93b2786865))
* provision to find and fix incorrect serial and batch bundles ([2276741](22767410d5))
* recalculate amount based on allocated amount ([2a90bff](2a90bffb5f))
* resolve conflicts ([97147a4](97147a484d))
* sabb missed in the incorrect serial no valuation report ([8a995f2](8a995f28c9))
* set default value for as zero for additional asset cost ([ee5e4ec](ee5e4eccec))
* set status to Draft for auto-created assets from Purchase Receipt ([d8eddbf](d8eddbfd03))
* stock difference value for adjustment entry ([6c0694f](6c0694ff17))
* **Task:** make Timesheet-dependent fields no_copy (backport [#50130](https://github.com/frappe/erpnext/issues/50130)) ([#50196](https://github.com/frappe/erpnext/issues/50196)) ([4988ff8](4988ff84df))
* use correct field name ([a82fa8c](a82fa8c26b))

### Features

* Add posting date param for reverse GL entries ([1a7092d](1a7092d7b6))
* add project filter to Delayed Tasks Summary report ([82ca729](82ca729e2b))
2025-10-28 13:48:43 +00:00
Diptanil Saha
0a09bb8d17 Merge pull request #50249 from frappe/version-15-hotfix
chore: release v15
2025-10-28 19:17:10 +05:30
rohitwaghchaure
ba700bce90 Merge pull request #50252 from frappe/mergify/bp/version-15-hotfix/pr-50247
fix: provision to find and fix incorrect serial and batch bundles (backport #50247)
2025-10-28 18:21:46 +05:30
Rohit Waghchaure
22767410d5 fix: provision to find and fix incorrect serial and batch bundles
(cherry picked from commit 10ad56060c)
2025-10-28 12:15:11 +00:00
Diptanil Saha
89e7a260fd Merge pull request #50251 from frappe/mergify/bp/version-15-hotfix/pr-50137
fix(payment-reco): recalculate amount based on allocated amount (backport #50137)
2025-10-28 17:34:10 +05:30
l0gesh29
2a90bffb5f fix: recalculate amount based on allocated amount
(cherry picked from commit 5a7a6a9bd5)
2025-10-28 11:58:10 +00:00
Diptanil Saha
7434522ea1 Merge pull request #50250 from frappe/mergify/bp/version-15-hotfix/pr-50220
fix(gross profit): remove customer name from columns (backport #50220)
2025-10-28 15:37:53 +05:30
ravibharathi656
158e1b28a9 fix: avoid group columns mutation
(cherry picked from commit 7ce81127d2)
2025-10-28 09:51:23 +00:00
ravibharathi656
a7672530f4 fix(gross profit): remove customer name from columns
(cherry picked from commit 0009925af0)
2025-10-28 09:51:23 +00:00
Diptanil Saha
ad33735541 Merge pull request #50245 from frappe/mergify/bp/version-15-hotfix/pr-50213
fix(Bank Transaction)!: make transaction ID non-unique (backport #50213)
2025-10-28 10:20:49 +05:30
Diptanil Saha
977f385006 chore: resolve conflict 2025-10-28 09:07:14 +05:30
barredterra
b52b04a10c fix(Bank Transaction): make transaction ID non-unique
(cherry picked from commit a2a41a0eaa)

# Conflicts:
#	erpnext/accounts/doctype/bank_transaction/bank_transaction.json
2025-10-28 02:45:52 +00:00
Diptanil Saha
05b89b4121 Merge pull request #50243 from frappe/mergify/bp/version-15-hotfix/pr-49599
fix(accounts): reference get_payment_mode_account correctly in Sales Invoice (backport #49599)
2025-10-28 08:08:36 +05:30
Diptanil Saha
36983c97cb chore: resolve conflict 2025-10-28 08:06:10 +05:30
KerollesFathy
23006601b2 fix(accounts): update payment mode account retrieval to use namespaced function
(cherry picked from commit 0af74aef00)

# Conflicts:
#	erpnext/public/js/controllers/accounts.js
2025-10-27 16:50:10 +00:00
rohitwaghchaure
37e9461ece Merge pull request #50239 from frappe/mergify/bp/version-15-hotfix/pr-50233
fix: optimized the slow query to get the batch-wise available qty (backport #50233)
2025-10-27 17:31:15 +05:30
Mihir Kandoi
126d79dcd2 Merge pull request #50241 from frappe/mergify/bp/version-15-hotfix/pr-50221
fix: Pass uom field name to update existing item qty (backport #50221)
2025-10-27 17:29:29 +05:30
Kavin
93b2786865 fix: Pass uom field name to update existing item qty
(cherry picked from commit 23d69389ec)
2025-10-27 11:48:44 +00:00
rohitwaghchaure
2680f85430 chore: fix linters issue 2025-10-27 17:06:45 +05:30
rohitwaghchaure
2d7fde024d chore: fix conflicts 2025-10-27 16:57:00 +05:30
Rohit Waghchaure
85bf9366b0 fix: optimized the slow query to get the batchwise available qty
(cherry picked from commit 9c21567309)

# Conflicts:
#	erpnext/stock/serial_batch_bundle.py
2025-10-27 11:19:24 +00:00
Khushi Rawat
86f507bb6e Merge pull request #50238 from frappe/mergify/bp/version-15-hotfix/pr-50222
fix(asset depreciations and balances): showing opening entries (backport #50222)
2025-10-27 16:35:46 +05:30
ravibharathi656
81f19b950d fix(asset depreciations and balances): showing opening entries
(cherry picked from commit d3afa67be3)
2025-10-27 10:48:36 +00:00
Mihir Kandoi
7b4eac2294 Merge pull request #50236 from Abdeali099/fix-msg
fix: add message for missing purchase orders in production plan
2025-10-27 15:54:09 +05:30
Diptanil Saha
aff4b62258 Merge pull request #50234 from frappe/mergify/bp/version-15-hotfix/pr-50039
fix(journal-entry): allow copy account currency when duplicating JV (backport #50039)
2025-10-27 15:36:40 +05:30
Abdeali Chharchhoda
5798409f69 fix: add message for missing purchase orders in production plan 2025-10-27 15:09:22 +05:30
Bhavan23
1b227b8b4f fix(journal-entry): allow copy account currency when duplicating JV
(cherry picked from commit 76748e4573)
2025-10-27 08:45:01 +00:00
Diptanil Saha
4eca7f0524 Merge pull request #50230 from frappe/mergify/bp/version-15-hotfix/pr-50103
fix: fiscal year overlap validation for company-specific years (backport #50103)
2025-10-27 11:01:57 +05:30
rehansari26
4bb66179ab test: replace get_doc with new_doc in fiscal year tests
(cherry picked from commit d59e55fb08)
2025-10-27 05:08:36 +00:00
Rehan Ansari
d84a29e1a1 test: fiscal year overlap validation for company-specific years
(cherry picked from commit 94ae098854)
2025-10-27 05:08:36 +00:00
Rehan Ansari
482a796212 fix: fiscal year overlap validation for company-specific years
(cherry picked from commit 57aaf34d3e)
2025-10-27 05:08:36 +00:00
Diptanil Saha
41694d3751 Merge pull request #50223 from frappe/mergify/bp/version-15-hotfix/pr-49889
feat: add project filter to Delayed Tasks Summary report (backport #49889)
2025-10-24 22:27:54 +05:30
Rehan Ansari
82ca729e2b feat: add project filter to Delayed Tasks Summary report
(cherry picked from commit 88097e78d2)
2025-10-24 16:42:15 +00:00
rohitwaghchaure
e67361257e Merge pull request #50216 from frappe/mergify/bp/version-15-hotfix/pr-50215
fix: get valuation rate based of previous SLEs for material receipt (backport #50215)
2025-10-24 00:05:12 +05:30
Rohit Waghchaure
e9f4a34d8d fix: get valuation rate based of previous SLEs for material receipt
(cherry picked from commit fa9ef6708f)
2025-10-23 18:30:20 +00:00
Khushi Rawat
c6c4e31b32 Merge pull request #50211 from frappe/mergify/bp/version-15-hotfix/pr-50209
fix: set status to Draft for auto-created assets from Purchase Receipt (backport #50209)
2025-10-23 23:33:42 +05:30
khushi8112
ee5e4eccec fix: set default value for as zero for additional asset cost 2025-10-23 22:51:30 +05:30
khushi8112
a82fa8c26b fix: use correct field name 2025-10-23 20:45:08 +05:30
khushi8112
97147a484d fix: resolve conflicts 2025-10-23 19:09:30 +05:30
khushi8112
d8eddbfd03 fix: set status to Draft for auto-created assets from Purchase Receipt
(cherry picked from commit 20c2cb40d1)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.py
#	erpnext/patches.txt
2025-10-23 13:03:17 +00:00
Deepesh Garg
977464e6a7 Merge pull request #50206 from frappe/mergify/bp/version-15-hotfix/pr-50204
feat: Add posting date param for reverse GL entries (#50204)
2025-10-23 16:02:07 +05:30
Deepesh Garg
db84df0cbd chore: Linting issues
(cherry picked from commit 05e1a737f1)
2025-10-23 08:12:31 +00:00
Deepesh Garg
1a7092d7b6 feat: Add posting date param for reverse GL entries
(cherry picked from commit 38988bf797)
2025-10-23 08:12:30 +00:00
rohitwaghchaure
fcd05bb7e4 Merge pull request #50202 from frappe/mergify/bp/version-15-hotfix/pr-50200
fix: stock difference value for adjustment entry (backport #50200)
2025-10-23 10:31:00 +05:30
Rohit Waghchaure
6c0694ff17 fix: stock difference value for adjustment entry
(cherry picked from commit fb4c7de86c)
2025-10-22 17:03:12 +00:00
mergify[bot]
4988ff84df fix(Task): make Timesheet-dependent fields no_copy (backport #50130) (#50196)
* fix(Task): make Timesheet-dependent fields no_copy

(cherry picked from commit 2a2e4b5423)

# Conflicts:
#	erpnext/projects/doctype/task/task.json

* chore: resolve conflicts

---------

Co-authored-by: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-10-22 08:34:11 +00:00
Mihir Kandoi
e5d0116924 Merge pull request #50194 from frappe/mergify/bp/version-15-hotfix/pr-50151
chore: remove print statement (backport #50151)
2025-10-22 14:01:04 +05:30
Mihir Kandoi
38d3b17dfe Merge pull request #50195 from frappe/mergify/bp/version-15-hotfix/pr-50153
fix: Address title error when create new transporter on driver  (backport #50153)
2025-10-22 13:48:31 +05:30
Kerolles Fathy
89d305ac9f Merge pull request #50153 from KerollesFathy/fix-address-error-when-create-transporter
fix: Address title error when create new transporter on driver
(cherry picked from commit 8efcf6cb38)
2025-10-22 08:14:25 +00:00
Abdeali Chharchhoda
8b2be43897 chore: remove print statement
(cherry picked from commit 1ad4dc9066)
2025-10-22 08:14:24 +00:00
rohitwaghchaure
0bd3b5aac2 Merge pull request #50192 from frappe/mergify/bp/version-15-hotfix/pr-50191
fix: sabb missed in the incorrect serial no valuation report (backport #50191)
2025-10-22 12:17:53 +05:30
Rohit Waghchaure
8a995f28c9 fix: sabb missed in the incorrect serial no valuation report
(cherry picked from commit b50bac6788)
2025-10-22 06:26:01 +00:00
Frappe PR Bot
e714b82802 chore(release): Bumped to Version 15.84.0
# [15.84.0](https://github.com/frappe/erpnext/compare/v15.83.2...v15.84.0) (2025-10-21)

### Bug Fixes

* added exception handling on service level agreement apply hook ([#50096](https://github.com/frappe/erpnext/issues/50096)) ([a3a6d39](a3a6d39436))
* adjustment entry ([76cfa28](76cfa28a42))
* correct monthly sales history (backport [#50056](https://github.com/frappe/erpnext/issues/50056)) ([#50179](https://github.com/frappe/erpnext/issues/50179)) ([afc2d95](afc2d95736))
* handle flt conversion for prev_ordered_qty ([3d3e116](3d3e116797))
* internal transfer entry with serial/batch ([d67a439](d67a439051))
* **point-of-sale:** render payment methods only payment component is visible ([2b0281c](2b0281c510))
* preview stock ledger for manual serial and batch values ([c64dcf3](c64dcf3423))
* **stock:** remove duplicate fields ([58a1383](58a1383380))
* validation for negative batch ([b9dd05f](b9dd05f292))

### Features

* set options for IBAN fields (backport [#49377](https://github.com/frappe/erpnext/issues/49377)) ([#49413](https://github.com/frappe/erpnext/issues/49413)) ([bd3a132](bd3a132868))

### Performance Improvements

* Add index for faster queries ([#50175](https://github.com/frappe/erpnext/issues/50175)) ([0571ed0](0571ed0735))
2025-10-21 12:44:58 +00:00
Diptanil Saha
7ec6ef3139 Merge pull request #50182 from frappe/version-15-hotfix
chore: release v15
2025-10-21 18:13:27 +05:30
mergify[bot]
afc2d95736 fix: correct monthly sales history (backport #50056) (#50179)
fix: correct monthly sales history (#50056)

* fix: correct monthly sales history

* fix: correct monthly sales history calculation

* chore: remove unrelated file accidentally committed

---------


(cherry picked from commit f51ed30c23)

Co-authored-by: Ahmed AbuKhatwa <82771130+AhmedAbokhatwa@users.noreply.github.com>
Co-authored-by: AhmedAbukhatwa <Ahmedabukhatwa1@gmail.com>
2025-10-21 11:06:02 +05:30
Deepesh Garg
1e9ffe351b Merge pull request #50178 from frappe/mergify/bp/version-15-hotfix/pr-50175
perf: Add index for faster queries
2025-10-21 09:56:35 +05:30
Deepesh Garg
0571ed0735 perf: Add index for faster queries (#50175)
(cherry picked from commit 7a91ec3e33)
2025-10-20 18:43:48 +00:00
mergify[bot]
43941ecd5e refactor: simplify expression (backport #50168) (#50170)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-10-19 23:59:27 +02:00
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
rohitwaghchaure
2055c4e8d3 Merge pull request #50157 from aerele/support-51015
fix(stock-settings): remove duplicate fields
2025-10-18 09:19:21 +05:30
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
rohitwaghchaure
df22902035 Merge pull request #50158 from frappe/mergify/bp/version-15-hotfix/pr-50156
fix: internal transfer entry with serial/batch (backport #50156)
2025-10-17 21:32:16 +05:30
Rohit Waghchaure
d67a439051 fix: internal transfer entry with serial/batch
(cherry picked from commit 9b4e62a758)
2025-10-17 15:34:30 +00:00
Kavin
58a1383380 fix(stock): remove duplicate fields 2025-10-17 18:35:53 +05:30
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
rohitwaghchaure
f5af5dd8c0 Merge pull request #50145 from frappe/mergify/bp/version-15-hotfix/pr-50123
fix: validation for negative batch (backport #50123)
2025-10-17 12:34:15 +05:30
Rohit Waghchaure
b9dd05f292 fix: validation for negative batch
(cherry picked from commit f9c8f27586)
2025-10-17 06:07:37 +00:00
Diptanil Saha
44f39e0b43 Merge pull request #50142 from frappe/mergify/bp/version-15-hotfix/pr-50141
fix(point-of-sale): render payment methods only when payment component is visible (backport #50141)
2025-10-17 01:46:05 +05:30
diptanilsaha
2b0281c510 fix(point-of-sale): render payment methods only payment component is visible
(cherry picked from commit 7dc4306640)
2025-10-16 20:13:33 +00:00
rohitwaghchaure
03766e8980 Merge pull request #50117 from frappe/mergify/bp/version-15-hotfix/pr-50113
fix: adjustment entry (backport #50113)
2025-10-15 21:31:32 +05:30
Diptanil Saha
e7b7377b7e Merge pull request #50120 from frappe/mergify/bp/version-15-hotfix/pr-50096
fix: added exception handling on service level agreement apply hook (backport #50096)
2025-10-15 19:51:23 +05:30
Diptanil Saha
a3a6d39436 fix: added exception handling on service level agreement apply hook (#50096)
* fix: added exception handling on service level agreement apply hook

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

This reverts commit dae93aa96f.

* fix: Ignore missing SLA table during install/uninstall

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
(cherry picked from commit 182c9fd966)
2025-10-15 14:04:17 +00:00
Rohit Waghchaure
76cfa28a42 fix: adjustment entry
(cherry picked from commit c0851abaee)
2025-10-15 10:30:33 +00:00
Diptanil Saha
afa7d2b595 Merge pull request #50109 from frappe/mergify/bp/version-15-hotfix/pr-50095
fix: preview stock ledger for manual serial and batch values (backport #50095)
2025-10-15 13:14:13 +05:30
Kavin
c64dcf3423 fix: preview stock ledger for manual serial and batch values
(cherry picked from commit c5f68d0b27)
2025-10-15 05:47:41 +00:00
Mihir Kandoi
3fa38d0b4e Merge pull request #50106 from frappe/mergify/bp/version-15-hotfix/pr-50078
fix: handle flt conversion for prev_ordered_qty (backport #50078)
2025-10-15 00:02:09 +05:30
mergify[bot]
abb210bd18 refactor: add supplier filter in buying (backport #50013) (#50107)
* refactor: add supplier filter in buying

(cherry picked from commit 108b108d64)

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

* chore: resolve conflicts

---------

Co-authored-by: manikandan-s-18 <manikandansundar18@gmail.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-10-14 18:22:20 +00:00
Kavin
3d3e116797 fix: handle flt conversion for prev_ordered_qty
(cherry picked from commit 77c35ef47f)
2025-10-14 18:10:30 +00:00
mergify[bot]
bd3a132868 feat: set options for IBAN fields (backport #49377) (#49413)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-10-14 14:42:08 +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
Frappe PR Bot
cc0a895760 chore(release): Bumped to Version 15.82.2
## [15.82.2](https://github.com/frappe/erpnext/compare/v15.82.1...v15.82.2) (2025-10-09)

### Bug Fixes

* Reset Raw Materials Table button not working ([00db37e](00db37e306))
* sales return for product bundle items ([2ea583e](2ea583ee16))
2025-10-09 12:31:16 +00:00
rohitwaghchaure
6ec370ad86 Merge pull request #49989 from frappe/mergify/bp/version-15/pr-49974
fix: Reset Raw Materials Table button not working (backport #49973) (backport #49974)
2025-10-09 17:59:42 +05:30
rohitwaghchaure
994f992fcf Merge pull request #49990 from frappe/mergify/bp/version-15/pr-49984
fix: sales return for product bundle items (backport #49975) (backport #49984)
2025-10-09 17:59:28 +05:30
rohitwaghchaure
3e816f6ea1 chore: fix conflicts
(cherry picked from commit 1fbc03c104)
2025-10-09 12:09:21 +00:00
Rohit Waghchaure
789cea9a85 test: test case for sales return for product bundle
(cherry picked from commit 1d57bbca11)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/test_delivery_note.py
(cherry picked from commit 6ba55bbee0)
2025-10-09 12:09:21 +00:00
Rohit Waghchaure
2ea583ee16 fix: sales return for product bundle items
(cherry picked from commit 13ce7279a8)
(cherry picked from commit ac46b3d1ca)
2025-10-09 12:09:20 +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
Rohit Waghchaure
00db37e306 fix: Reset Raw Materials Table button not working
(cherry picked from commit 128e243945)
(cherry picked from commit 81ed32ff51)
2025-10-09 12:06:21 +00:00
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
Frappe PR Bot
c84952169f chore(release): Bumped to Version 15.82.1
## [15.82.1](https://github.com/frappe/erpnext/compare/v15.82.0...v15.82.1) (2025-10-08)

### Bug Fixes

* batch qty for expired batches ([4e2a42d](4e2a42d092))
* incorrect field valuation_rate ([9c3f4b6](9c3f4b69af))
2025-10-08 14:32:21 +00:00
rohitwaghchaure
d103409077 Merge pull request #49972 from frappe/mergify/bp/version-15/pr-49971
fix: batch qty for expired batches (backport #49967) (backport #49971)
2025-10-08 20:00:34 +05:30
rohitwaghchaure
d1dd3ca097 Merge pull request #49970 from frappe/mergify/bp/version-15/pr-49968
fix: incorrect field valuation_rate (backport #49966) (backport #49968)
2025-10-08 20:00:19 +05:30
Rohit Waghchaure
4e2a42d092 fix: batch qty for expired batches
(cherry picked from commit ff2faf36a7)
(cherry picked from commit f4816e4960)
2025-10-08 14:12:42 +00:00
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
Rohit Waghchaure
9c3f4b69af fix: incorrect field valuation_rate
(cherry picked from commit 630d873214)
(cherry picked from commit 93df11a0cf)
2025-10-08 13:38:13 +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
Frappe PR Bot
e2e1ac0dba chore(release): Bumped to Version 15.82.0
# [15.82.0](https://github.com/frappe/erpnext/compare/v15.81.3...v15.82.0) (2025-10-07)

### Bug Fixes

* **accounting:** ensure proper removal of advance references during unreconcillation ([825ccd3](825ccd3422))
* add default scrap warehouse in wo ([a5ed9fd](a5ed9fdc67))
* Add try-finally for setting buying price list ([e21baec](e21baec246))
* check is_rejected attribute ([bdf150b](bdf150bdf8))
* clear asset custodian  when asset take back from employee without assign to another employee ([f4cdb49](f4cdb49126))
* **Common Code:** fetch canonical URI from Code List (backport [#49882](https://github.com/frappe/erpnext/issues/49882)) ([#49884](https://github.com/frappe/erpnext/issues/49884)) ([2c0501b](2c0501b05f))
* delete column dynamically based on the naming by ([040873a](040873a442))
* do not consider draft bundles ([75323fd](75323fda01))
* do not fetch disabled item tax template ([c42dcbe](c42dcbe739))
* exclude opening entries ([ed1c270](ed1c270398))
* failing patch ([0dad195](0dad1957c8))
* get unconsumed qty as per BOM qty ([82386b1](82386b18aa))
* linter; dont change doc after DB update ([#49907](https://github.com/frappe/erpnext/issues/49907)) ([6c47353](6c47353205))
* **manufacturing:** prevent KeyError in BOM Creator when sub-assembly reused ([07c3755](07c3755f31))
* optimize SQL query by adding index on batch ([a83331b](a83331bd2f))
* **profit and loss statement:** incorrect total calculation ([e132c45](e132c457f2))
* remove allow_on_submit for pick list items ([4ccdede](4ccdedeb12))
* resolved conflict ([a39bc62](a39bc626c7))
* retain shipping address in doc ([20c21a4](20c21a4dc0))
* set fg_completed_qty based upon fg item qty ([b6d57ff](b6d57ff8a5))
* Set paid amount automatically only if return entry validated and has negative grand total ([#49829](https://github.com/frappe/erpnext/issues/49829)) ([bd3503a](bd3503a3d8))
* SQL operator precedence in Project query customer filter ([d950de2](d950de2d09))
* **subscription:** include days before ([f27b754](f27b754570))
* too many writes on patch run ([a2bf53f](a2bf53ff0a))
* use item valuation rate if no bin ([a4b5a74](a4b5a74644))
* validate transfer_qty based on overproduction wo percentage ([7ce97ce](7ce97ce0c2))
* warning message if the batch has incorrect qty ([3f3fd20](3f3fd20b31))

### Features

* allow fallback to default selling price list (backport [#49634](https://github.com/frappe/erpnext/issues/49634)) ([#49704](https://github.com/frappe/erpnext/issues/49704)) ([95387b4](95387b4bf0))
* dynamic due date in payment terms when fetched from order (backport [#48864](https://github.com/frappe/erpnext/issues/48864)) ([#49938](https://github.com/frappe/erpnext/issues/49938)) ([baa6d2b](baa6d2bcdc))
* recalculate batch qty ([d49b64d](d49b64dc7c))
* validating asset scrap date ([#43093](https://github.com/frappe/erpnext/issues/43093)) ([0eb76f4](0eb76f4d2c))

### Performance Improvements

* serial nos / batches reposting ([8a310ef](8a310efc97))
2025-10-07 13:23:00 +00:00
ruthra kumar
b557a03ba7 Merge pull request #49942 from frappe/version-15-hotfix
chore: release v15
2025-10-07 18:51:39 +05:30
mergify[bot]
11eab0c852 Merge pull request #49946 from frappe/mergify/bp/version-15-hotfix/pr-49721
feat: add company links to Email Account and Communication (backport #49721)
2025-10-07 17:01:37 +05:30
rohitwaghchaure
8683f49e8b Merge pull request #49945 from frappe/mergify/bp/version-15-hotfix/pr-49935
refactor: old serial nos filter (backport #49935)
2025-10-07 16:29:16 +05:30
rohitwaghchaure
6ea07ba56d chore: fix conflicts 2025-10-07 16:02:30 +05:30
Rohit Waghchaure
e02a55b188 refactor: old serial nos filter
(cherry picked from commit 6a8bd0ae9e)

# Conflicts:
#	erpnext/stock/deprecated_serial_batch.py
2025-10-07 09:52:27 +00:00
mergify[bot]
baa6d2bcdc feat: dynamic due date in payment terms when fetched from order (backport #48864) (#49938)
* 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/public/js/controllers/transaction.js
#	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:15 +05:30
rohitwaghchaure
631ffd55ef Merge pull request #49934 from frappe/mergify/bp/version-15-hotfix/pr-49846
chore(Stock Qty vs Serial No Count): add show_disabled_items filter (backport #49846)
2025-10-07 11:49:20 +05:30
rethik
7a457dafe0 chore: add show_disabled_items filter to show both enabled and disabled items
(cherry picked from commit bf5f24c0e0)
2025-10-07 05:59:36 +00:00
rohitwaghchaure
94a5867303 Merge pull request #49933 from frappe/mergify/bp/version-15-hotfix/pr-49762
fix(manufacturing): prevent KeyError in BOM Creator when sub-assembly reused (backport #49762)
2025-10-07 11:23:12 +05:30
rohitwaghchaure
1663b875c0 Merge pull request #49932 from frappe/mergify/bp/version-15-hotfix/pr-49702
fix: do not fetch disabled item tax template (backport #49702)
2025-10-07 11:22:57 +05:30
ljain112
d47f3cc101 chore: resolve conflicts 2025-10-07 11:01:47 +05:30
KerollesFathy
07c3755f31 fix(manufacturing): prevent KeyError in BOM Creator when sub-assembly reused
Ensure missing (fg_item, fg_reference_id) keys are initialized in
production_item_wise_rm before appending items. This avoids crashes
when the same sub-assembly is referenced under multiple parents.

(cherry picked from commit 4f8b2e520a)
2025-10-07 05:27:18 +00:00
ljain112
c42dcbe739 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
rohitwaghchaure
233fe9b7e6 Merge pull request #49929 from frappe/mergify/bp/version-15-hotfix/pr-49928
fix: warning message if the batch has incorrect qty (backport #49928)
2025-10-07 10:42:23 +05:30
Rohit Waghchaure
3f3fd20b31 fix: warning message if the batch has incorrect qty
(cherry picked from commit 870181de87)
2025-10-07 04:50:35 +00:00
ruthra kumar
f84a6c0d3a Merge pull request #49927 from frappe/mergify/bp/version-15-hotfix/pr-49743
fix: use valuation_rate from item master if no bin is present (backport #49743)
2025-10-07 10:03:27 +05:30
ruthra kumar
5c9c17c649 Merge pull request #49925 from frappe/mergify/bp/version-15-hotfix/pr-49870
fix: show asset value as revaluation amount or gross purchase amount (backport #49870)
2025-10-07 10:02:28 +05:30
ravibharathi656
a4b5a74644 fix: use item valuation rate if no bin
(cherry picked from commit 23b1b7ee04)
2025-10-07 04:13:26 +00:00
l0gesh29
ed1c270398 fix: exclude opening entries
(cherry picked from commit 3773f56b0b)
2025-10-06 21:40:58 +00:00
rohitwaghchaure
56d04761f8 Merge pull request #49924 from frappe/mergify/bp/version-15-hotfix/pr-49923
feat: recalculate batch qty (backport #49923)
2025-10-06 22:33:22 +05:30
Rohit Waghchaure
d49b64dc7c feat: recalculate batch qty
(cherry picked from commit 70117d3b06)
2025-10-06 16:30:14 +00:00
Frappe PR Bot
42b43b372a chore(release): Bumped to Version 15.81.3
## [15.81.3](https://github.com/frappe/erpnext/compare/v15.81.2...v15.81.3) (2025-10-06)

### Bug Fixes

* check is_rejected attribute ([b017f07](b017f07343))
* do not consider draft bundles ([92afae7](92afae7185))
2025-10-06 14:45:22 +00:00
rohitwaghchaure
4dae4b987e Merge pull request #49922 from frappe/mergify/bp/version-15/pr-49918
fix: check is_rejected attribute (backport #49913) (backport #49918)
2025-10-06 20:13:46 +05:30
rohitwaghchaure
54e672e078 Merge pull request #49921 from frappe/mergify/bp/version-15/pr-49919
fix: do not consider draft bundles (backport #49917) (backport #49919)
2025-10-06 20:13:38 +05:30
rohitwaghchaure
62a09a3cb0 Merge pull request #49920 from frappe/mergify/bp/version-15-hotfix/pr-49890
perf: serial nos / batches reposting (backport #49890)
2025-10-06 19:58:25 +05:30
Kavin
b017f07343 fix: check is_rejected attribute
(cherry picked from commit 2ac2e02b2f)
(cherry picked from commit bdf150bdf8)
2025-10-06 14:25:55 +00:00
Rohit Waghchaure
92afae7185 fix: do not consider draft bundles
(cherry picked from commit a60f7eaf3a)
(cherry picked from commit 75323fda01)
2025-10-06 14:25:46 +00:00
rohitwaghchaure
eb9d656b8f Merge pull request #49918 from frappe/mergify/bp/version-15-hotfix/pr-49913
fix: check is_rejected attribute (backport #49913)
2025-10-06 19:54:59 +05:30
rohitwaghchaure
7ddf63faa8 Merge pull request #49919 from frappe/mergify/bp/version-15-hotfix/pr-49917
fix: do not consider draft bundles (backport #49917)
2025-10-06 19:54:51 +05:30
Rohit Waghchaure
8a310efc97 perf: serial nos / batches reposting
(cherry picked from commit acb3ef78a7)
2025-10-06 14:11:43 +00:00
Kavin
bdf150bdf8 fix: check is_rejected attribute
(cherry picked from commit 2ac2e02b2f)
2025-10-06 14:08:30 +00:00
Rohit Waghchaure
75323fda01 fix: do not consider draft bundles
(cherry picked from commit a60f7eaf3a)
2025-10-06 14:08:30 +00:00
ruthra kumar
f94f628884 Merge pull request #49912 from frappe/mergify/bp/version-15-hotfix/pr-49600
fix(profit and loss statement): incorrect total calculation (backport #49600)
2025-10-06 17:27:55 +05:30
ravibharathi656
e132c457f2 fix(profit and loss statement): incorrect total calculation
(cherry picked from commit b7c6d8e2a6)
2025-10-06 11:40:38 +00:00
ruthra kumar
048d6f6942 Merge pull request #49908 from frappe/mergify/bp/version-15-hotfix/pr-49635
fix(subscription): include days before (backport #49635)
2025-10-06 15:39:59 +05:30
Raffael Meyer
6c47353205 fix: linter; dont change doc after DB update (#49907)
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-10-06 10:02:05 +00:00
ravibharathi656
4f067085e7 test: add invoice generation before period with prorate
(cherry picked from commit b452e06b82)
2025-10-06 09:52:07 +00:00
ravibharathi656
3fcbb10155 refactor(subscription): default prorate 0
(cherry picked from commit eda1dae882)
2025-10-06 09:52:07 +00:00
ravibharathi656
f27b754570 fix(subscription): include days before
(cherry picked from commit 9164162a9e)
2025-10-06 09:52:06 +00:00
mergify[bot]
95387b4bf0 feat: allow fallback to default selling price list (backport #49634) (#49704)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Henning Wendtland <156231187+HenningWendtland@users.noreply.github.com>
2025-10-06 11:49:40 +02:00
ruthra kumar
7f2d26ec20 Merge pull request #49900 from frappe/mergify/bp/version-15-hotfix/pr-49852
fix: SQL operator precedence in Project query customer filter (backport #49852)
2025-10-06 13:21:58 +05:30
Fawaz Alhafiz
d950de2d09 fix: SQL operator precedence in Project query customer filter
Added explicit parentheses around customer OR conditions in get_project_name()
to ensure proper grouping with AND filters. Without these parentheses, SQL
operator precedence caused the status filter to be bypassed when a customer
filter was applied, resulting in completed and cancelled projects appearing
in link field dropdowns.

Before:
WHERE customer='X' OR customer IS NULL OR customer='' AND status NOT IN (...)
was interpreted as:
WHERE customer='X' OR customer IS NULL OR (customer='' AND status NOT IN (...))

After:
WHERE (customer='X' OR customer IS NULL OR customer='') AND status NOT IN (...)

Fixes: Completed/cancelled projects showing in Project link fields
Affected: Any doctype using Project link fields with customer filters
(cherry picked from commit 0ec30a1cea)
2025-10-06 07:34:55 +00:00
ruthra kumar
5d8a368ad2 Merge pull request #49893 from frappe/mergify/bp/version-15-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:33 +05:30
ruthra kumar
615a5fa5ba Merge pull request #49898 from frappe/mergify/bp/version-15-hotfix/pr-49865
fix: delete column dynamically based on the naming by (backport #49865)
2025-10-06 12:51:23 +05:30
ruthra kumar
823cdf211d Merge pull request #49894 from frappe/mergify/bp/version-15-hotfix/pr-49871
fix: retain shipping address in doc (backport #49871)
2025-10-06 12:37:21 +05:30
ravibharathi656
20c21a4dc0 fix: retain shipping address in doc
(cherry picked from commit 039f5e6143)

# Conflicts:
#	erpnext/public/js/controllers/buying.js
2025-10-06 12:35:21 +05:30
l0gesh29
040873a442 fix: delete column dynamically based on the naming by
(cherry picked from commit 4f503ac7f6)
2025-10-06 07:05:04 +00:00
Nabin Hait
a39bc626c7 fix: resolved conflict 2025-10-06 12:17:57 +05:30
Nabin Hait
bd3503a3d8 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:29 +00:00
Frappe PR Bot
161c1fb7a4 chore(release): Bumped to Version 15.81.2
## [15.81.2](https://github.com/frappe/erpnext/compare/v15.81.1...v15.81.2) (2025-10-06)

### Bug Fixes

* failing patch ([65061cc](65061cc459))
2025-10-06 03:54:42 +00:00
ruthra kumar
4d506d2d9c Merge pull request #49873 from frappe/mergify/bp/version-15/pr-49868
fix: failing patch (backport #49867) (backport #49868)
2025-10-06 09:23:17 +05:30
ruthra kumar
04abc6b5e5 Merge pull request #49877 from frappe/mergify/bp/version-15-hotfix/pr-49872
fix: remove allow_on_submit for pick list items (backport #49872)
2025-10-06 09:21:33 +05:30
mergify[bot]
2c0501b05f fix(Common Code): fetch canonical URI from Code List (backport #49882) (#49884)
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:28 +02:00
rohitwaghchaure
7b484de0a9 Merge pull request #49878 from frappe/mergify/bp/version-15-hotfix/pr-49876
fix: optimize SQL query by adding index on batch (backport #49876)
2025-10-04 13:18:07 +05:30
Rohit Waghchaure
a83331bd2f fix: optimize SQL query by adding index on batch
(cherry picked from commit 8756f91857)
2025-10-04 05:30:10 +00:00
Kavin
4ccdedeb12 fix: remove allow_on_submit for pick list items
(cherry picked from commit da716b824f)
2025-10-04 04:36:08 +00:00
Mihir Kandoi
65061cc459 fix: failing patch
(cherry picked from commit 41d1703e7c)
(cherry picked from commit 0dad1957c8)
2025-10-03 19:04:15 +00:00
Mihir Kandoi
fe0739aadf Merge pull request #49868 from frappe/mergify/bp/version-15-hotfix/pr-49867
fix: failing patch (backport #49867)
2025-10-04 00:33:18 +05:30
Mihir Kandoi
0dad1957c8 fix: failing patch
(cherry picked from commit 41d1703e7c)
2025-10-03 10:02:09 +00:00
rohitwaghchaure
bf39ae6db0 Merge pull request #49856 from frappe/mergify/bp/version-15-hotfix/pr-49806
fix: validate transfer_qty based on overproduction wo percentage (backport #49806)
2025-10-02 22:43:52 +05:30
Khushi Rawat
09bcff13ad Merge pull request #49522 from KerollesFathy/clear-custodian-when-asset-take-backed
fix: clear asset custodian when asset take back from employee without assign to another employee
2025-10-02 22:16:35 +05:30
rohitwaghchaure
b91fb6b280 Merge pull request #49855 from frappe/mergify/bp/version-15-hotfix/pr-49850
fix: add default scrap warehouse in wo (backport #49850)
2025-10-02 20:22:20 +05:30
Kavin
0fec34e886 test: test overproduction allowed qty in wo
(cherry picked from commit b527d38bfa)
2025-10-02 14:36:24 +00:00
Kavin
b6d57ff8a5 fix: set fg_completed_qty based upon fg item qty
(cherry picked from commit 526b850e61)
2025-10-02 14:36:24 +00:00
Kavin
7ce97ce0c2 fix: validate transfer_qty based on overproduction wo percentage
(cherry picked from commit 4024d8846b)
2025-10-02 14:36:23 +00:00
Kavin
a5ed9fdc67 fix: add default scrap warehouse in wo
(cherry picked from commit 7e51346946)
2025-10-02 14:35:22 +00:00
mergify[bot]
5e5850d89a refactor(Supplier): custom buttons call make methods (backport #49840) (#49842)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-10-02 00:16:34 +02:00
Diptanil Saha
56aa86f77e Merge pull request #49839 from diptanilsaha/backport_49496
fix(Accounts Payable Summary): add a missing translate function (backport #49496)
2025-10-02 00:24:42 +05:30
Diptanil Saha
f321725b49 Merge pull request #49496 from elshafei-developer/Add-a-missing-translate-function
fix(Accounts Payable Summary): add a missing translate function
2025-10-02 00:07:06 +05:30
Diptanil Saha
4e3697284e Merge pull request #49838 from diptanilsaha/backport_49820
fix: financial ratios translation and pdf export error (backport #49820)
2025-10-01 23:57:09 +05:30
Frappe PR Bot
4c1cada222 chore(release): Bumped to Version 15.81.1
## [15.81.1](https://github.com/frappe/erpnext/compare/v15.81.0...v15.81.1) (2025-10-01)

### Bug Fixes

* Add try-finally for setting buying price list ([e10ed89](e10ed89be5))
* too many writes on patch run ([c455e73](c455e7390e))
2025-10-01 14:55:26 +00:00
Mihir Kandoi
8bb19eece4 Merge pull request #49835 from frappe/mergify/bp/version-15/pr-49833
fix: too many writes on patch run (backport #49832) (backport #49833)
2025-10-01 20:23:45 +05:30
Mihir Kandoi
e10ed89be5 fix: Add try-finally for setting buying price list
(cherry picked from commit 35a8d02866)
(cherry picked from commit e21baec246)
2025-10-01 13:44:41 +00:00
Mihir Kandoi
c455e7390e fix: too many writes on patch run
(cherry picked from commit 44ff6ed6a1)
(cherry picked from commit a2bf53ff0a)
2025-10-01 13:44:41 +00:00
Mihir Kandoi
3f9fbb7b75 Merge pull request #49833 from frappe/mergify/bp/version-15-hotfix/pr-49832 2025-10-01 19:13:21 +05:30
Mihir Kandoi
e21baec246 fix: Add try-finally for setting buying price list
(cherry picked from commit 35a8d02866)
2025-10-01 13:27:40 +00:00
Mihir Kandoi
a2bf53ff0a fix: too many writes on patch run
(cherry picked from commit 44ff6ed6a1)
2025-10-01 13:27:40 +00:00
ruthra kumar
06cd48feaa Merge pull request #49823 from frappe/mergify/bp/version-15-hotfix/pr-49689
fix(accounting): ensure proper removal of advance references during u… (backport #49689)
2025-10-01 12:56:19 +05:30
Pandiyan P
825ccd3422 fix(accounting): ensure proper removal of advance references during unreconcillation
(cherry picked from commit a7ec01bf21)
2025-10-01 06:40:55 +00:00
rohitwaghchaure
c56da286f7 Merge pull request #49811 from frappe/mergify/bp/version-15-hotfix/pr-49750
fix: get unconsumed qty as per BOM required qty (backport #49750)
2025-09-30 22:19:55 +05:30
Khushi Rawat
a7577d428f Merge pull request #49817 from khushi8112/backport-v15-pr-43093
feat: validating asset scrap date (backport #43093)
2025-09-30 21:23:26 +05:30
Khushi Rawat
e81b85b241 chore: resolve conflict 2025-09-30 21:02:39 +05:30
rahulgupta8848
0eb76f4d2c feat: validating asset scrap date (#43093)
* feat: validating asset scrap date

* refactor: refactorization of scrap asset function

---------

Co-authored-by: “rahulgupta8848” <“rahul.gupta@8848digital.com”>
(cherry picked from commit e07bc5af41)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.js
2025-09-30 21:02:15 +05:30
Frappe PR Bot
24013ddf27 chore(release): Bumped to Version 15.81.0
# [15.81.0](https://github.com/frappe/erpnext/compare/v15.80.1...v15.81.0) (2025-09-30)

### Bug Fixes

* add date filter for getting return invoice items ([bc77704](bc77704462))
* auto batch not set for raw materials in subcontracting receipt ([6c8e838](6c8e8384d5))
* **bank transaction:** change reference number to small text ([5a0bf7a](5a0bf7a5e1))
* convert with flt ([3975627](3975627b33))
* do not validate cost center in cancelled gl entry ([366bb82](366bb8269c))
* don't recalculate stock_qty with conversion_factor ([c88a277](c88a277ad9))
* include precision in validation ([abffbca](abffbcad99))
* **payment entry:** trigger currency on account set ([84a2596](84a2596ce5))
* remove item name to avoid overriding item row name ([d0e511f](d0e511fd2f))
* set cost center in taxes if not set ([4dcd502](4dcd502b76))
* **stock entry:** set expense account from company for manufacture ([50f9521](50f9521feb))
* update item details only in draft state ([30f6b53](30f6b53240))
* update subcontracted_quantity with set_value ([17e415d](17e415d03a))
* use get_value instead of get_doc ([f706811](f706811757))
* use sales_order from data instead of doc ([7ee8281](7ee82813fd))
* use stock adjustment account  if no expense account ([9c81f44](9c81f448c8))
* valuation rate for old batch ([37a03f1](37a03f10ab))

### Features

* add show zero value filter in profit and loss and balance sheet ([c32ad97](c32ad9711f))
* service expense account in the subcontracting receipt ([ba9b63a](ba9b63af49))

### Performance Improvements

* reposting for backdated transactions ([cd5a836](cd5a836685))
2025-09-30 13:28:59 +00:00
ruthra kumar
e8ee5f8c9b Merge pull request #49801 from frappe/version-15-hotfix
chore: release v15
2025-09-30 18:56:45 +05:30
rohitwaghchaure
0fb5f75e93 chore: fix conflicts
Removed the test for additional material transfer in work orders.
2025-09-30 18:43:38 +05:30
rohitwaghchaure
4847e24553 Merge branch 'version-15' into version-15-hotfix 2025-09-30 18:38:20 +05:30
Kavin
f548f0b231 test: required_qty clamping in manufacture entry
(cherry picked from commit 34d2c8d9c2)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/test_work_order.py
2025-09-30 12:28:34 +00:00
Kavin
82386b18aa fix: get unconsumed qty as per BOM qty
(cherry picked from commit cf4b395ee3)
2025-09-30 12:28:33 +00:00
rohitwaghchaure
bbbcb456ce Merge pull request #49810 from frappe/mergify/bp/version-15-hotfix/pr-49803
fix: valuation rate for old batch (backport #49803)
2025-09-30 17:50:13 +05:30
rohitwaghchaure
4c78f9b9cb Merge pull request #49807 from frappe/mergify/bp/version-15-hotfix/pr-49794
fix: don't recalculate stock_qty with conversion_factor (backport #49794)
2025-09-30 17:12:18 +05:30
Rohit Waghchaure
37a03f10ab fix: valuation rate for old batch
(cherry picked from commit d864d166f9)
2025-09-30 11:42:15 +00:00
rohitwaghchaure
c2dd37a89e Merge pull request #49808 from frappe/mergify/bp/version-15-hotfix/pr-49766
fix: use sales_order from data instead of doc (backport #49766)
2025-09-30 17:12:07 +05:30
rohitwaghchaure
f458faa819 Merge pull request #49809 from frappe/mergify/bp/version-15-hotfix/pr-49748
fix: update item details only in draft state (backport #49748)
2025-09-30 17:11:56 +05:30
Kavin
30f6b53240 fix: update item details only in draft state
(cherry picked from commit 689172ff22)
2025-09-30 11:08:59 +00:00
Kavin
7ee82813fd fix: use sales_order from data instead of doc
(cherry picked from commit 9f9120451b)
2025-09-30 11:06:48 +00:00
Kavin
c88a277ad9 fix: don't recalculate stock_qty with conversion_factor
(cherry picked from commit fed8236919)
2025-09-30 11:03:47 +00:00
rohitwaghchaure
9e73d083b5 Merge pull request #49793 from frappe/mergify/bp/version-15-hotfix/pr-49790
fix: update subcontracted_quantity with set_value (backport #49790)
2025-09-30 16:32:13 +05:30
rohitwaghchaure
03c8d92796 Merge pull request #49792 from frappe/mergify/bp/version-15-hotfix/pr-49791
refactor: convert item warehouse based reposting (backport #49791)
2025-09-30 16:31:57 +05:30
ruthra kumar
31ad9a6346 Merge pull request #49805 from frappe/mergify/bp/version-15-hotfix/pr-49639
fix(Credit-limit): consider current voucher for credit limit validation (backport #49639)
2025-09-30 16:30:17 +05:30
PRASATHRAJA
fc40a3c376 Merge pull request #49639 from aerele/credit-limit-jv
fix(Credit-limit): consider current voucher for credit limit validation

(cherry picked from commit 4a01c53cca)
2025-09-30 10:39:35 +00:00
ruthra kumar
b8e3db0179 Merge pull request #49800 from frappe/mergify/bp/version-15-hotfix/pr-49799
fix: do not validate cost center in cancelled gl entry (backport #49799)
2025-09-30 15:14:11 +05:30
ljain112
366bb8269c fix: do not validate cost center in cancelled gl entry
(cherry picked from commit 29cbddbc77)
2025-09-30 09:04:24 +00:00
venkat102
3975627b33 fix: convert with flt
(cherry picked from commit 81614939ab)
2025-09-29 16:20:27 +00:00
venkat102
f706811757 fix: use get_value instead of get_doc
(cherry picked from commit 89a603f20c)
2025-09-29 16:20:26 +00:00
venkat102
17e415d03a fix: update subcontracted_quantity with set_value
(cherry picked from commit ea63bfc9af)
2025-09-29 16:20:26 +00:00
Rohit Waghchaure
8911bad792 refactor: convert item-wh based reposting
(cherry picked from commit 8411e4c5b2)
2025-09-29 16:20:25 +00:00
ruthra kumar
a30c481ca1 Merge pull request #49787 from frappe/mergify/bp/version-15-hotfix/pr-49773
fix(bank transaction): change reference number to small text (backport #49773)
2025-09-29 17:15:06 +05:30
ravibharathi656
5a0bf7a5e1 fix(bank transaction): change reference number to small text
(cherry picked from commit 90f399d0fc)

# Conflicts:
#	erpnext/accounts/doctype/bank_transaction/bank_transaction.json
2025-09-29 16:59:31 +05:30
ruthra kumar
f135954944 Merge pull request #49784 from frappe/mergify/bp/version-15-hotfix/pr-49708
feat: add show zero value filter in profit and loss and balance sheet (backport #49708)
2025-09-29 16:47:54 +05:30
ravibharathi656
c32ad9711f feat: add show zero value filter in profit and loss and balance sheet
(cherry picked from commit 33ab24943c)

# Conflicts:
#	erpnext/accounts/report/balance_sheet/balance_sheet.js
#	erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
2025-09-29 16:32:21 +05:30
ruthra kumar
f6842041c9 Merge pull request #49782 from frappe/mergify/bp/version-15-hotfix/pr-49718
fix: set cost center in taxes if not set (backport #49718)
2025-09-29 16:26:35 +05:30
ravibharathi656
4dcd502b76 fix: set cost center in taxes if not set
(cherry picked from commit b75940bf0e)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
2025-09-29 15:53:19 +05:30
ruthra kumar
19f81c9204 Merge pull request #49777 from frappe/mergify/bp/version-15-hotfix/pr-49735
fix(payment entry): trigger currency on account set (backport #49735)
2025-09-29 14:56:14 +05:30
ruthra kumar
67b6b3779d Merge pull request #49775 from frappe/mergify/bp/version-15-hotfix/pr-49618
fix: add date filter for getting return invoice items (backport #49618)
2025-09-29 14:31:45 +05:30
ruthra kumar
c836016358 Merge pull request #49774 from frappe/mergify/bp/version-15-hotfix/pr-49640
fix: include precision in validation (backport #49640)
2025-09-29 14:29:45 +05:30
ravibharathi656
84a2596ce5 fix(payment entry): trigger currency on account set
(cherry picked from commit 096e74b1ee)
2025-09-29 08:58:03 +00:00
Navin-S-R
98bc7b41fc test: validate profit values for later period returns
(cherry picked from commit d5c457b8c5)

# Conflicts:
#	erpnext/accounts/report/gross_profit/test_gross_profit.py
2025-09-29 14:16:52 +05:30
l0gesh29
abffbcad99 fix: include precision in validation
(cherry picked from commit 1de0c46c51)
2025-09-29 08:44:29 +00:00
Navin-S-R
bc77704462 fix: add date filter for getting return invoice items
(cherry picked from commit 2abb011816)
2025-09-29 08:44:28 +00:00
rohitwaghchaure
5abb2d510d Merge pull request #49772 from frappe/mergify/bp/version-15-hotfix/pr-49770
chore: removed print statement (backport #49770)
2025-09-29 13:06:44 +05:30
Rohit Waghchaure
ae040024af chore: removed print statement
(cherry picked from commit 324bdcb177)
2025-09-29 06:32:32 +00:00
rohitwaghchaure
fe1247dd4b Merge pull request #49758 from frappe/mergify/bp/version-15-hotfix/pr-49757
feat: service expense account in the subcontracting receipt (backport #49757)
2025-09-27 10:20:23 +05:30
rohitwaghchaure
84789daf59 Merge pull request #49756 from frappe/mergify/bp/version-15-hotfix/pr-49741
fix(stock entry): set expense account from company for manufacture (backport #49741)
2025-09-27 10:04:06 +05:30
rohitwaghchaure
cecd177286 chore: fix conflicts
Removed fields related to job card and landed cost voucher amount from subcontracting receipt item.
2025-09-27 10:03:27 +05:30
rohitwaghchaure
c26ed80d67 chore: fix conflicts 2025-09-27 10:02:40 +05:30
rohitwaghchaure
15f0c28cda chore: fix conflicts 2025-09-27 10:01:53 +05:30
Rohit Waghchaure
ba9b63af49 feat: service expense account in the subcontracting receipt
(cherry picked from commit 6e597b9c42)

# Conflicts:
#	erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
#	erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
#	erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
2025-09-26 15:44:26 +00:00
ravibharathi656
9c81f448c8 fix: use stock adjustment account if no expense account
(cherry picked from commit b2e109318f)
2025-09-26 13:28:43 +00:00
ravibharathi656
50f9521feb fix(stock entry): set expense account from company for manufacture
(cherry picked from commit 06177ffaff)
2025-09-26 13:28:43 +00:00
ruthra kumar
0ff32ac554 Merge pull request #49738 from frappe/mergify/bp/version-15-hotfix/pr-49717
refactor: improve trial balance performance (backport #49717)
2025-09-26 11:58:26 +05:30
rohitwaghchaure
fe72ea6d6b Merge pull request #49739 from frappe/mergify/bp/version-15-hotfix/pr-49734
fix: auto batch not set for raw materials in subcontracting receipt (backport #49734)
2025-09-26 11:43:04 +05:30
rohitwaghchaure
62959e5a70 chore: fix conflicts 2025-09-26 11:21:16 +05:30
Rohit Waghchaure
6c8e8384d5 fix: auto batch not set for raw materials in subcontracting receipt
(cherry picked from commit 23f9d4c600)

# Conflicts:
#	erpnext/controllers/subcontracting_controller.py
2025-09-26 05:07:12 +00:00
ruthra kumar
0c8004530a refactor: improve trial balance performance
(cherry picked from commit cee3813ced)
2025-09-26 04:46:00 +00:00
Raffael Meyer
b18afa8bdd chore(Bank Guarante): move to leaner doctype definition (#49737) 2025-09-25 22:05:59 +00:00
rohitwaghchaure
6b3bb45900 Merge pull request #49728 from frappe/mergify/bp/version-15-hotfix/pr-49725
fix: remove item name in get_item_details (backport #49725)
2025-09-25 19:52:28 +05:30
Kavin
d0e511fd2f fix: remove item name to avoid overriding item row name
(cherry picked from commit 47055901c0)
2025-09-25 13:23:29 +00:00
rohitwaghchaure
d50538dbf7 Merge pull request #49724 from frappe/mergify/bp/version-15-hotfix/pr-49720
perf: reposting for backdated transactions (backport #49720)
2025-09-25 18:52:32 +05:30
Rohit Waghchaure
cd5a836685 perf: reposting for backdated transactions
(cherry picked from commit 1b0fc0541b)
2025-09-25 12:16:41 +00:00
KerollesFathy
4bc76be130 refactor: optimize asset location and custodian update logic to avoid multiple updates 2025-09-10 19:01:14 +03:00
KerollesFathy
f4cdb49126 fix: clear asset custodian when asset take back from employee without assign to another employee 2025-09-10 18:27:39 +03:00
144 changed files with 2570 additions and 1676 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.80.1"
__version__ = "15.85.1"
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

@@ -132,7 +132,8 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "IBAN",
"length": 30
"length": 34,
"options": "IBAN"
},
{
"fieldname": "column_break_12",
@@ -208,6 +209,7 @@
"label": "Disabled"
}
],
"grid_page_length": 50,
"links": [
{
"group": "Transactions",
@@ -250,7 +252,7 @@
"link_fieldname": "default_bank_account"
}
],
"modified": "2024-10-30 09:41:14.113414",
"modified": "2025-08-29 12:32:01.081687",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account",
@@ -282,6 +284,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "bank,account",
"sort_field": "modified",
"sort_order": "DESC",

View File

@@ -9,7 +9,8 @@ from frappe.contacts.address_and_contact import (
load_address_and_contact,
)
from frappe.model.document import Document
from frappe.utils import comma_and, get_link_to_form
from frappe.utils import comma_and, get_link_to_form, validate_iban
from frappe.utils.deprecations import deprecated
class BankAccount(Document):
@@ -52,7 +53,6 @@ class BankAccount(Document):
def validate(self):
self.validate_company()
self.validate_iban()
self.validate_account()
self.update_default_bank_account()
@@ -72,34 +72,10 @@ class BankAccount(Document):
if self.is_company_account and not self.company:
frappe.throw(_("Company is manadatory for company account"))
@deprecated
def validate_iban(self):
"""
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
"""
# IBAN field is optional
if not self.iban:
return
def encode_char(c):
# Position in the alphabet (A=1, B=2, ...) plus nine
return str(9 + ord(c) - 64)
# remove whitespaces, upper case to get the right number from ord()
iban = "".join(self.iban.split(" ")).upper()
# Move country code and checksum from the start to the end
flipped = iban[4:] + iban[:4]
# Encode characters as numbers
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
try:
to_check = int("".join(encoded))
except ValueError:
frappe.throw(_("IBAN is not valid"))
if to_check % 97 != 1:
frappe.throw(_("IBAN is not valid"))
"""Kept for backward compatibility, will be removed in v16."""
validate_iban(self.iban, throw=True)
def update_default_bank_account(self):
if self.is_default and not self.disabled:

View File

@@ -3,45 +3,8 @@
import unittest
import frappe
from frappe import ValidationError
# test_records = frappe.get_test_records('Bank Account')
class TestBankAccount(unittest.TestCase):
def test_validate_iban(self):
valid_ibans = [
"GB82 WEST 1234 5698 7654 32",
"DE91 1000 0000 0123 4567 89",
"FR76 3000 6000 0112 3456 7890 189",
]
invalid_ibans = [
# wrong checksum (3rd place)
"GB72 WEST 1234 5698 7654 32",
"DE81 1000 0000 0123 4567 89",
"FR66 3000 6000 0112 3456 7890 189",
]
bank_account = frappe.get_doc({"doctype": "Bank Account"})
try:
bank_account.validate_iban()
except AttributeError:
msg = "BankAccount.validate_iban() failed for empty IBAN"
self.fail(msg=msg)
for iban in valid_ibans:
bank_account.iban = iban
try:
bank_account.validate_iban()
except ValidationError:
msg = f"BankAccount.validate_iban() failed for valid IBAN {iban}"
self.fail(msg=msg)
for not_iban in invalid_ibans:
bank_account.iban = not_iban
msg = f"BankAccount.validate_iban() accepted invalid IBAN {not_iban}"
with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban()
pass

File diff suppressed because it is too large Load Diff

View File

@@ -116,15 +116,14 @@
{
"allow_on_submit": 1,
"fieldname": "reference_number",
"fieldtype": "Data",
"fieldtype": "Small Text",
"label": "Reference Number"
},
{
"fieldname": "transaction_id",
"fieldtype": "Data",
"label": "Transaction ID",
"read_only": 1,
"unique": 1
"read_only": 1
},
{
"allow_on_submit": 1,
@@ -223,7 +222,8 @@
{
"fieldname": "bank_party_iban",
"fieldtype": "Data",
"label": "Party IBAN (Bank Statement)"
"label": "Party IBAN (Bank Statement)",
"options": "IBAN"
},
{
"fieldname": "bank_party_account_number",
@@ -238,7 +238,7 @@
"grid_page_length": 50,
"is_submittable": 1,
"links": [],
"modified": "2025-06-18 17:24:57.044666",
"modified": "2025-10-23 17:32:58.514807",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",

View File

@@ -36,7 +36,7 @@ class BankTransaction(Document):
party: DF.DynamicLink | None
party_type: DF.Link | None
payment_entries: DF.Table[BankTransactionPayments]
reference_number: DF.Data | None
reference_number: DF.SmallText | None
status: DF.Literal["", "Pending", "Settled", "Unreconciled", "Reconciled", "Cancelled"]
transaction_id: DF.Data | None
transaction_type: DF.Data | None

View File

@@ -99,7 +99,7 @@ class FiscalYear(Document):
)
overlap = False
if not self.get("companies") or not company_for_existing:
if not self.get("companies") and not company_for_existing:
overlap = True
for d in self.get("companies"):

View File

@@ -26,6 +26,27 @@ class TestFiscalYear(unittest.TestCase):
self.assertRaises(frappe.exceptions.InvalidDates, fy.insert)
def test_company_fiscal_year_overlap(self):
for name in ["_Test Global FY 2001", "_Test Company FY 2001"]:
if frappe.db.exists("Fiscal Year", name):
frappe.delete_doc("Fiscal Year", name)
global_fy = frappe.new_doc("Fiscal Year")
global_fy.year = "_Test Global FY 2001"
global_fy.year_start_date = "2001-04-01"
global_fy.year_end_date = "2002-03-31"
global_fy.insert()
company_fy = frappe.new_doc("Fiscal Year")
company_fy.year = "_Test Company FY 2001"
company_fy.year_start_date = "2001-01-01"
company_fy.year_end_date = "2001-12-31"
company_fy.append("companies", {"company": "_Test Company"})
company_fy.insert()
self.assertTrue(frappe.db.exists("Fiscal Year", global_fy.name))
self.assertTrue(frappe.db.exists("Fiscal Year", company_fy.name))
def test_record_generator():
test_records = [

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(
@@ -253,7 +253,7 @@ class GLEntry(Document):
)
def validate_cost_center(self):
if not self.cost_center:
if not self.cost_center or self.is_cancelled:
return
is_group, company = frappe.get_cached_value("Cost Center", self.cost_center, ["is_group", "company"])

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
@@ -189,8 +190,8 @@ class JournalEntry(AccountsController):
def on_submit(self):
self.validate_cheque_info()
self.check_credit_limit()
self.make_gl_entries()
self.check_credit_limit()
self.update_asset_value()
self.update_inter_company_jv()
self.update_invoice_discounting()
@@ -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

@@ -11,6 +11,7 @@ from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction
from erpnext.exceptions import InvalidAccountCurrency
from erpnext.selling.doctype.customer.test_customer import make_customer, set_credit_limit
class TestJournalEntry(unittest.TestCase):
@@ -592,6 +593,15 @@ class TestJournalEntry(unittest.TestCase):
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver 2")
def test_credit_limit_for_customer(self):
customer = make_customer("_Test New Customer")
set_credit_limit("_Test New Customer", "_Test Company", 50)
jv = make_journal_entry(account1="Debtors - _TC", account2="_Test Cash - _TC", amount=100, save=False)
jv.accounts[0].party_type = "Customer"
jv.accounts[0].party = customer
jv.save()
self.assertRaises(frappe.ValidationError, jv.submit)
def make_journal_entry(
account1,

View File

@@ -106,7 +106,6 @@
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
"read_only": 1
@@ -272,7 +271,8 @@
"label": "Advance Voucher Type",
"no_copy": 1,
"options": "DocType",
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "advance_voucher_no",
@@ -280,13 +280,14 @@
"label": "Advance Voucher No",
"no_copy": 1,
"options": "advance_voucher_type",
"read_only": 1
"read_only": 1,
"search_index": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-07-25 04:45:28.117715",
"modified": "2025-10-27 13:48:32.805100",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -601,6 +601,7 @@ frappe.ui.form.on("Payment Entry", {
if (frm.doc.payment_type == "Pay") {
frm.events.paid_amount(frm);
}
frm.events.paid_from_account_currency(frm);
}
);
},
@@ -624,6 +625,7 @@ frappe.ui.form.on("Payment Entry", {
frm.events.received_amount(frm);
}
}
frm.events.paid_to_account_currency(frm);
}
);
},

View File

@@ -385,6 +385,16 @@ frappe.ui.form.on("Payment Reconciliation Allocation", {
// filter payment
let payment = frm.doc.payments.filter((x) => x.reference_name == row.reference_name);
let amount = payment[0].amount;
for (const d of frm.doc.allocation) {
if (row.reference_name == d.reference_name && amount) {
if (d.allocated_amount <= amount) {
d.amount = amount;
amount -= d.allocated_amount;
}
}
}
frm.call({
doc: frm.doc,
method: "calculate_difference_on_allocation_change",

View File

@@ -226,7 +226,8 @@
"fetch_from": "bank_account.iban",
"fieldname": "iban",
"fieldtype": "Read Only",
"label": "IBAN"
"label": "IBAN",
"options": "IBAN"
},
{
"fetch_from": "bank_account.branch_code",
@@ -443,11 +444,12 @@
"label": "Phone Number"
}
],
"grid_page_length": 50,
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-12-27 21:29:10.361894",
"modified": "2025-08-29 11:52:48.555415",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
@@ -482,8 +484,9 @@
"write": 1
}
],
"row_format": "Dynamic",
"show_preview_popup": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -127,7 +127,13 @@ class PaymentRequest(Document):
existing_payment_request_amount = flt(get_existing_payment_request_amount(ref_doc))
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
if (
flt(
existing_payment_request_amount + flt(self.grand_total, self.precision("grand_total")),
get_currency_precision(),
)
> ref_amount
):
frappe.throw(
_("Total Payment Request amount cannot be greater than {0} amount").format(
self.reference_doctype

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",
@@ -172,12 +177,50 @@
"label": "Paid Amount (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"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": "2025-03-11 11:06:51.792982",
"modified": "2025-07-31 08:38:25.820701",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
@@ -189,4 +232,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -17,12 +17,27 @@ class PaymentSchedule(Document):
base_outstanding: DF.Currency
base_paid_amount: DF.Currency
base_payment_amount: DF.Currency
credit_days: DF.Int
credit_months: DF.Int
description: DF.SmallText | None
discount: DF.Float
discount_date: DF.Date | None
discount_type: DF.Literal["Percentage", "Amount"]
discount_validity: DF.Int
discount_validity_based_on: DF.Literal[
"",
"Day(s) after invoice date",
"Day(s) after the end of the invoice month",
"Month(s) after the end of the invoice month",
]
discounted_amount: DF.Currency
due_date: DF.Date
due_date_based_on: DF.Literal[
"",
"Day(s) after invoice date",
"Day(s) after the end of the invoice month",
"Month(s) after the end of the invoice month",
]
invoice_portion: DF.Percent
mode_of_payment: DF.Link | None
outstanding: DF.Currency

View File

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

View File

@@ -161,6 +161,8 @@ def get_pos_invoices(start, end, pos_profile, user):
`tabPOS Invoice`
where
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = ''
order by
timestamp
""",
(user, pos_profile),
as_dict=1,

View File

@@ -2150,19 +2150,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",
@@ -2188,7 +2185,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",
@@ -2644,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

@@ -483,18 +483,23 @@ class Subscription(Document):
return invoice
def get_items_from_plans(self, plans: list[dict[str, str]], prorate: bool | None = None) -> list[dict]:
def get_items_from_plans(self, plans: list[dict[str, str]], prorate: int = 0) -> list[dict]:
"""
Returns the `Item`s linked to `Subscription Plan`
"""
if prorate is None:
prorate = False
prorate_factor = 1
if prorate:
prorate_factor = get_prorata_factor(
self.current_invoice_end,
self.current_invoice_start,
cint(self.generate_invoice_at == "Beginning of the current subscription period"),
cint(
self.generate_invoice_at
in [
"Beginning of the current subscription period",
"Days before the current subscription period",
]
),
)
items = []
@@ -511,33 +516,19 @@ class Subscription(Document):
deferred = frappe.db.get_value("Item", item_code, deferred_field)
if not prorate:
item = {
"item_code": item_code,
"qty": plan.qty,
"rate": get_plan_rate(
plan.plan,
plan.qty,
party,
self.current_invoice_start,
self.current_invoice_end,
),
"cost_center": plan_doc.cost_center,
}
else:
item = {
"item_code": item_code,
"qty": plan.qty,
"rate": get_plan_rate(
plan.plan,
plan.qty,
party,
self.current_invoice_start,
self.current_invoice_end,
prorate_factor,
),
"cost_center": plan_doc.cost_center,
}
item = {
"item_code": item_code,
"qty": plan.qty,
"rate": get_plan_rate(
plan.plan,
plan.qty,
party,
self.current_invoice_start,
self.current_invoice_end,
prorate_factor,
),
"cost_center": plan_doc.cost_center,
}
if deferred:
item.update(

View File

@@ -8,6 +8,7 @@ from frappe.utils.data import (
add_days,
add_months,
add_to_date,
add_years,
cint,
date_diff,
flt,
@@ -555,6 +556,33 @@ class TestSubscription(FrappeTestCase):
subscription.reload()
self.assertEqual(len(subscription.invoices), 0)
def test_invoice_generation_days_before_subscription_period_with_prorate(self):
settings = frappe.get_single("Subscription Settings")
settings.prorate = 1
settings.save()
create_plan(
plan_name="_Test Plan Name 5",
cost=1000,
billing_interval="Year",
billing_interval_count=1,
currency="INR",
)
start_date = add_days(nowdate(), 2)
subscription = create_subscription(
start_date=start_date,
party_type="Supplier",
party="_Test Supplier",
generate_invoice_at="Days before the current subscription period",
generate_new_invoices_past_due_date=1,
number_of_days=2,
plans=[{"plan": "_Test Plan Name 5", "qty": 1}],
)
subscription.process(nowdate())
self.assertEqual(len(subscription.invoices), 1)
def make_plans():
create_plan(plan_name="_Test Plan Name", cost=900, currency="INR")

View File

@@ -648,6 +648,7 @@ def make_reverse_gl_entries(
adv_adj=False,
update_outstanding="Yes",
partial_cancel=False,
posting_date=None,
):
"""
Get original gl entries of the voucher
@@ -745,6 +746,8 @@ def make_reverse_gl_entries(
if immutable_ledger_enabled:
new_gle["is_cancelled"] = 0
new_gle["posting_date"] = frappe.form_dict.get("posting_date") or getdate()
elif posting_date:
new_gle["posting_date"] = posting_date
if new_gle["debit"] or new_gle["credit"]:
make_entry(new_gle, adv_adj, "Yes")

View File

@@ -1270,7 +1270,7 @@ class ReceivablePayableReport:
def setup_ageing_columns(self):
# for charts
self.ageing_column_labels = []
ranges = [*self.ranges, "Above"]
ranges = [*self.ranges, _("Above")]
prev_range_value = 0
for idx, curr_range_value in enumerate(ranges):

View File

@@ -171,7 +171,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
self.add_column(_("Difference"), fieldname="diff")
self.setup_ageing_columns()
self.add_column(label="Total Amount Due", fieldname="total_due")
self.add_column(label=_("Total Amount Due"), fieldname="total_due")
if self.filters.show_future_payments:
self.add_column(label=_("Future Payment Amount"), fieldname="future_amount")

View File

@@ -273,6 +273,7 @@ def get_asset_value_adjustment_map_by_category(filters):
AND a.company = %(company)s
AND a.purchase_date <= %(to_date)s
AND gle.account = aca.fixed_asset_account
AND gle.is_opening = 'No'
GROUP BY a.asset_category
""",
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
@@ -354,7 +355,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
@@ -543,6 +544,7 @@ def get_asset_value_adjustment_map(filters):
AND a.company = %(company)s
AND a.purchase_date <= %(to_date)s
AND gle.account = aca.fixed_asset_account
AND gle.is_opening = 'No'
GROUP BY a.name
""",
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
@@ -583,6 +585,14 @@ def get_columns(filters):
"width": 120,
}
)
columns.append(
{
"label": _("Asset Name"),
"fieldname": "asset_name",
"fieldtype": "Data",
"width": 140,
}
)
columns += [
{

View File

@@ -5,28 +5,33 @@ frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statement
erpnext.utils.add_dimensions("Balance Sheet", 10);
frappe.query_reports["Balance Sheet"]["filters"].push({
fieldname: "selected_view",
label: __("Select View"),
fieldtype: "Select",
options: [
{ value: "Report", label: __("Report View") },
{ value: "Growth", label: __("Growth View") },
],
default: "Report",
reqd: 1,
});
frappe.query_reports["Balance Sheet"]["filters"].push({
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
});
frappe.query_reports["Balance Sheet"]["filters"].push({
fieldname: "include_default_book_entries",
label: __("Include Default FB Entries"),
fieldtype: "Check",
default: 1,
});
frappe.query_reports["Balance Sheet"]["filters"].push(
{
fieldname: "selected_view",
label: __("Select View"),
fieldtype: "Select",
options: [
{ value: "Report", label: __("Report View") },
{ value: "Growth", label: __("Growth View") },
],
default: "Report",
reqd: 1,
},
{
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
},
{
fieldname: "include_default_book_entries",
label: __("Include Default FB Entries"),
fieldtype: "Check",
default: 1,
},
{
fieldname: "show_zero_values",
label: __("Show zero values"),
fieldtype: "Check",
}
);

View File

@@ -52,7 +52,7 @@ frappe.query_reports["Financial Ratios"] = {
},
],
formatter: function (value, row, column, data, default_formatter) {
let heading_ratios = ["Liquidity Ratios", "Solvency Ratios", "Turnover Ratios"];
let heading_ratios = [__("Liquidity Ratios"), __("Solvency Ratios"), __("Turnover Ratios")];
if (heading_ratios.includes(value)) {
value = $(`<span>${value}</span>`);
@@ -60,7 +60,7 @@ frappe.query_reports["Financial Ratios"] = {
value = $value.wrap("<p></p>").parent().html();
}
if (heading_ratios.includes(row[1].content) && column.fieldtype == "Float") {
if (heading_ratios.includes(row[1]?.content) && column.fieldtype == "Float") {
column.fieldtype = "Data";
}

View File

@@ -147,9 +147,9 @@ def get_gl_data(filters, period_list, years):
def add_liquidity_ratios(data, years, current_asset, current_liability, quick_asset):
precision = frappe.db.get_single_value("System Settings", "float_precision")
data.append({"ratio": "Liquidity Ratios"})
data.append({"ratio": _("Liquidity Ratios")})
ratio_data = [["Current Ratio", current_asset], ["Quick Ratio", quick_asset]]
ratio_data = [[_("Current Ratio"), current_asset], [_("Quick Ratio"), quick_asset]]
for d in ratio_data:
row = {
@@ -165,13 +165,13 @@ def add_solvency_ratios(
data, years, total_asset, total_liability, net_sales, cogs, total_income, total_expense
):
precision = frappe.db.get_single_value("System Settings", "float_precision")
data.append({"ratio": "Solvency Ratios"})
data.append({"ratio": _("Solvency Ratios")})
debt_equity_ratio = {"ratio": "Debt Equity Ratio"}
gross_profit_ratio = {"ratio": "Gross Profit Ratio"}
net_profit_ratio = {"ratio": "Net Profit Ratio"}
return_on_asset_ratio = {"ratio": "Return on Asset Ratio"}
return_on_equity_ratio = {"ratio": "Return on Equity Ratio"}
debt_equity_ratio = {"ratio": _("Debt Equity Ratio")}
gross_profit_ratio = {"ratio": _("Gross Profit Ratio")}
net_profit_ratio = {"ratio": _("Net Profit Ratio")}
return_on_asset_ratio = {"ratio": _("Return on Asset Ratio")}
return_on_equity_ratio = {"ratio": _("Return on Equity Ratio")}
for year in years:
profit_after_tax = flt(total_income.get(year)) + flt(total_expense.get(year))
@@ -195,7 +195,7 @@ def add_solvency_ratios(
def add_turnover_ratios(data, years, period_list, filters, total_asset, net_sales, cogs, direct_expense):
precision = frappe.db.get_single_value("System Settings", "float_precision")
data.append({"ratio": "Turnover Ratios"})
data.append({"ratio": _("Turnover Ratios")})
avg_data = {}
for d in ["Receivable", "Payable", "Stock"]:
@@ -208,10 +208,10 @@ def add_turnover_ratios(data, years, period_list, filters, total_asset, net_sale
)
ratio_data = [
["Fixed Asset Turnover Ratio", net_sales, total_asset],
["Debtor Turnover Ratio", net_sales, avg_debtors],
["Creditor Turnover Ratio", direct_expense, avg_creditors],
["Inventory Turnover Ratio", cogs, avg_stock],
[_("Fixed Asset Turnover Ratio"), net_sales, total_asset],
[_("Debtor Turnover Ratio"), net_sales, avg_debtors],
[_("Creditor Turnover Ratio"), direct_expense, avg_creditors],
[_("Inventory Turnover Ratio"), cogs, avg_stock],
]
for ratio in ratio_data:
row = {

View File

@@ -212,7 +212,7 @@ def get_data(
company_currency,
accumulated_values=filters.accumulated_values,
)
out = filter_out_zero_value_rows(out, parent_children_map)
out = filter_out_zero_value_rows(out, parent_children_map, filters.show_zero_values)
if out and total:
add_total_row(out, root_type, balance_must_be, period_list, company_currency)
@@ -323,18 +323,24 @@ def prepare_data(accounts, balance_must_be, period_list, company_currency, accum
def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False):
def get_all_parents(account, parent_children_map):
for parent, children in parent_children_map.items():
for child in children:
if child["name"] == account and parent:
accounts_to_show.add(parent)
get_all_parents(parent, parent_children_map)
data_with_value = []
accounts_to_show = set()
for d in data:
if show_zero_values or d.get("has_value"):
accounts_to_show.add(d.get("account"))
get_all_parents(d.get("account"), parent_children_map)
for d in data:
if d.get("account") in accounts_to_show:
data_with_value.append(d)
else:
# show group with zero balance, if there are balances against child
children = [child.name for child in parent_children_map.get(d.get("account")) or []]
if children:
for row in data:
if row.get("account") in children and row.get("has_value"):
data_with_value.append(d)
break
return data_with_value

View File

@@ -178,7 +178,12 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
# to display item as Item Code: Item Name
columns[0] = "Sales Invoice:Link/Item:300"
# removing Item Code and Item Name columns
del columns[4:6]
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
if supplier_master_name == "Supplier Name" and customer_master_name == "Customer Name":
del columns[4:6]
else:
del columns[5:7]
total_base_amount = 0
total_buying_amount = 0
@@ -226,6 +231,15 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
group_columns = group_wise_columns.get(scrub(filters.group_by))
# removing customer_name from group columns
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
if "customer_name" in group_columns and (
supplier_master_name == "Supplier Name" and customer_master_name == "Customer Name"
):
group_columns = [col for col in group_columns if col != "customer_name"]
for src in gross_profit_data.grouped_data:
total_base_amount += src.base_amount or 0.00
total_buying_amount += src.buying_amount or 0.00
@@ -275,7 +289,7 @@ def get_columns(group_wise_columns, filters):
"label": _("Posting Date"),
"fieldname": "posting_date",
"fieldtype": "Date",
"width": 100,
"width": 120,
},
"posting_time": {
"label": _("Posting Time"),
@@ -677,7 +691,9 @@ class GrossProfitGenerator:
si.name = si_item.parent
and si.docstatus = 1
and si.is_return = 1
and si.posting_date between %(from_date)s and %(to_date)s
""",
{"from_date": self.filters.from_date, "to_date": self.filters.to_date},
as_dict=1,
)

View File

@@ -1,9 +1,9 @@
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import flt, nowdate
from frappe.utils import add_days, flt, get_first_day, get_last_day, nowdate
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note, make_sales_return
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.gross_profit.gross_profit import execute
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
@@ -392,7 +392,6 @@ class TestGrossProfit(FrappeTestCase):
"""
Item Qty for Sales Invoices with multiple instances of same item go in the -ve. Ideally, the credit noteshould cancel out the invoice items.
"""
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
# Invoice with an item added twice
sinv = self.create_sales_invoice(qty=1, rate=100, posting_date=nowdate(), do_not_submit=True)
@@ -635,3 +634,42 @@ class TestGrossProfit(FrappeTestCase):
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 100.0)
self.assertEqual(total.get("gross_profit_%"), 100.0)
def test_profit_for_later_period_return(self):
month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
# create sales invoice on month start date
sinv = self.create_sales_invoice(qty=1, rate=100, do_not_save=True, do_not_submit=True)
sinv.set_posting_time = 1
sinv.posting_date = month_start_date
sinv.save().submit()
# create credit note on next month start date
cr_note = make_sales_return(sinv.name)
cr_note.set_posting_time = 1
cr_note.posting_date = add_days(month_end_date, 1)
cr_note.save().submit()
# apply filters for invoiced period
filters = frappe._dict(
company=self.company, from_date=month_start_date, to_date=month_end_date, group_by="Invoice"
)
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total.selling_amount, 100.0)
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 100.0)
self.assertEqual(total.get("gross_profit_%"), 100.0)
# extend filters upto returned period
filters.update(to_date=add_days(month_end_date, 1))
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total.selling_amount, 0.0)
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 0.0)
self.assertEqual(total.get("gross_profit_%"), 0.0)

View File

@@ -5,29 +5,34 @@ frappe.query_reports["Profit and Loss Statement"] = $.extend({}, erpnext.financi
erpnext.utils.add_dimensions("Profit and Loss Statement", 10);
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
fieldname: "selected_view",
label: __("Select View"),
fieldtype: "Select",
options: [
{ value: "Report", label: __("Report View") },
{ value: "Growth", label: __("Growth View") },
{ value: "Margin", label: __("Margin View") },
],
default: "Report",
reqd: 1,
});
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
});
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
fieldname: "include_default_book_entries",
label: __("Include Default FB Entries"),
fieldtype: "Check",
default: 1,
});
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
{
fieldname: "selected_view",
label: __("Select View"),
fieldtype: "Select",
options: [
{ value: "Report", label: __("Report View") },
{ value: "Growth", label: __("Growth View") },
{ value: "Margin", label: __("Margin View") },
],
default: "Report",
reqd: 1,
},
{
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
},
{
fieldname: "include_default_book_entries",
label: __("Include Default FB Entries"),
fieldtype: "Check",
default: 1,
},
{
fieldname: "show_zero_values",
label: __("Show zero values"),
fieldtype: "Check",
}
);

View File

@@ -216,7 +216,7 @@ def get_opening_balance(
ignore_is_opening=0,
):
closing_balance = frappe.qb.DocType(doctype)
account = frappe.qb.DocType("Account")
accounts = frappe.db.get_all("Account", filters={"report_type": report_type}, pluck="name")
opening_balance = (
frappe.qb.from_(closing_balance)
@@ -228,14 +228,7 @@ def get_opening_balance(
Sum(closing_balance.debit_in_account_currency).as_("debit_in_account_currency"),
Sum(closing_balance.credit_in_account_currency).as_("credit_in_account_currency"),
)
.where(
(closing_balance.company == filters.company)
& (
closing_balance.account.isin(
frappe.qb.from_(account).select("name").where(account.report_type == report_type)
)
)
)
.where((closing_balance.company == filters.company) & (closing_balance.account.isin(accounts)))
.groupby(closing_balance.account)
)
@@ -290,21 +283,24 @@ def get_opening_balance(
if filters.project:
opening_balance = opening_balance.where(closing_balance.project == filters.project)
if filters.get("include_default_book_entries"):
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
if frappe.db.count("Finance Book"):
if filters.get("include_default_book_entries"):
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
frappe.throw(
_("To use a different finance book, please uncheck 'Include Default FB Entries'")
)
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
| (closing_balance.finance_book.isnull())
)
else:
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
| (closing_balance.finance_book.isnull())
)
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
| (closing_balance.finance_book.isnull())
)
else:
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
| (closing_balance.finance_book.isnull())
)
if accounting_dimensions:
for dimension in accounting_dimensions:

View File

@@ -947,19 +947,28 @@ def update_accounting_ledgers_after_reference_removal(
adv_ple.run()
def remove_ref_from_advance_section(ref_doc: object = None):
def remove_ref_from_advance_section(ref_doc: object = None, payment_name: str | None = None):
# TODO: this might need some testing
if ref_doc.doctype in ("Sales Invoice", "Purchase Invoice"):
ref_doc.set("advances", [])
adv_type = qb.DocType(f"{ref_doc.doctype} Advance")
qb.from_(adv_type).delete().where(adv_type.parent == ref_doc.name).run()
row_names = []
for adv in ref_doc.get("advances") or []:
if adv.get("reference_name", None) == payment_name:
row_names.append(adv.name)
if not row_names:
return
child_table = (
"Sales Invoice Advance" if ref_doc.doctype == "Sales Invoice" else "Purchase Invoice Advance"
)
frappe.db.delete(child_table, {"name": ("in", row_names)})
def unlink_ref_doc_from_payment_entries(ref_doc: object = None, payment_name: str | None = None):
remove_ref_doc_link_from_jv(ref_doc.doctype, ref_doc.name, payment_name)
remove_ref_doc_link_from_pe(ref_doc.doctype, ref_doc.name, payment_name)
update_accounting_ledgers_after_reference_removal(ref_doc.doctype, ref_doc.name, payment_name)
remove_ref_from_advance_section(ref_doc)
remove_ref_from_advance_section(ref_doc, payment_name)
def remove_ref_doc_link_from_jv(
@@ -1026,7 +1035,6 @@ def remove_ref_doc_link_from_pe(
query = query.where(per.parent == payment_name)
reference_rows = query.run(as_dict=True)
if not reference_rows:
return

View File

@@ -790,17 +790,33 @@ frappe.ui.form.on("Asset Finance Book", {
});
erpnext.asset.scrap_asset = function (frm) {
frappe.confirm(__("Do you really want to scrap this asset?"), function () {
frappe.call({
args: {
asset_name: frm.doc.name,
var scrap_dialog = new frappe.ui.Dialog({
title: __("Enter date to scrap asset"),
fields: [
{
label: __("Select the date"),
fieldname: "scrap_date",
fieldtype: "Date",
reqd: 1,
},
method: "erpnext.assets.doctype.asset.depreciation.scrap_asset",
callback: function (r) {
cur_frm.reload_doc();
},
});
],
size: "medium",
primary_action_label: "Submit",
primary_action(values) {
frappe.call({
args: {
asset_name: frm.doc.name,
scrap_date: values.scrap_date,
},
method: "erpnext.assets.doctype.asset.depreciation.scrap_asset",
callback: function (r) {
frm.reload_doc();
scrap_dialog.hide();
},
});
},
});
scrap_dialog.show();
};
erpnext.asset.restore_asset = function (frm) {

View File

@@ -518,6 +518,7 @@
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:doc.docstatus > 0",
"fieldname": "additional_asset_cost",
"fieldtype": "Currency",
@@ -596,7 +597,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2025-05-20 00:44:06.229177",
"modified": "2025-10-23 22:43:33.634452",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
@@ -641,4 +642,4 @@
"states": [],
"title_field": "asset_name",
"track_changes": 1
}
}

View File

@@ -69,6 +69,7 @@ class Asset(AccountsController):
default_finance_book: DF.Link | None
department: DF.Link | None
depr_entry_posting_status: DF.Literal["", "Successful", "Failed"]
depreciation_completed: DF.Check
depreciation_method: DF.Literal["", "Straight Line", "Double Declining Balance", "Manual"]
disposal_date: DF.Date | None
finance_books: DF.Table[AssetFinanceBook]
@@ -152,7 +153,9 @@ class Asset(AccountsController):
)
self.validate_expected_value_after_useful_life()
self.set_total_booked_depreciations()
self.total_asset_cost = self.gross_purchase_amount
def before_save(self):
self.total_asset_cost = self.gross_purchase_amount + self.additional_asset_cost
self.status = self.get_status()
def on_submit(self):

View File

@@ -394,7 +394,7 @@ def get_comma_separated_links(names, doctype):
@frappe.whitelist()
def scrap_asset(asset_name):
def scrap_asset(asset_name, scrap_date=None):
asset = frappe.get_doc("Asset", asset_name)
if asset.docstatus != 1:
@@ -402,7 +402,11 @@ def scrap_asset(asset_name):
elif asset.status in ("Cancelled", "Sold", "Scrapped", "Capitalized"):
frappe.throw(_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status))
date = today()
today_date = getdate(today())
date = getdate(scrap_date) or today_date
purchase_date = getdate(asset.purchase_date)
validate_scrap_date(date, today_date, purchase_date, asset.calculate_depreciation, asset_name)
notes = _("This schedule was created when Asset {0} was scrapped.").format(
get_link_to_form(asset.doctype, asset.name)
@@ -436,6 +440,36 @@ def scrap_asset(asset_name):
frappe.msgprint(_("Asset scrapped via Journal Entry {0}").format(je.name))
def validate_scrap_date(scrap_date, today_date, purchase_date, calculate_depreciation, asset_name):
if scrap_date > today_date:
frappe.throw(_("Future date is not allowed"))
elif scrap_date < purchase_date:
frappe.throw(_("Scrap date cannot be before purchase date"))
if calculate_depreciation:
asset_depreciation_schedules = frappe.db.get_all(
"Asset Depreciation Schedule", filters={"asset": asset_name, "docstatus": 1}, fields=["name"]
)
for depreciation_schedule in asset_depreciation_schedules:
last_booked_depreciation_date = frappe.db.get_value(
"Depreciation Schedule",
{
"parent": depreciation_schedule["name"],
"docstatus": 1,
"journal_entry": ["!=", ""],
},
"schedule_date",
order_by="schedule_date desc",
)
if (
last_booked_depreciation_date
and scrap_date < last_booked_depreciation_date
and scrap_date > purchase_date
):
frappe.throw(_("Asset cannot be scrapped before the last depreciation entry."))
@frappe.whitelist()
def restore_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
@@ -473,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

@@ -15,6 +15,7 @@ from frappe.utils import (
is_last_day_of_the_month,
nowdate,
)
from frappe.utils.data import add_to_date
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
@@ -219,6 +220,31 @@ class TestAsset(AssetSetup):
)
self.assertEqual(accumulated_depr_amount, 18000.0)
asset_depreciation = frappe.db.get_value(
"Asset Depreciation Schedule", {"asset": asset.name, "docstatus": 1}, "name"
)
last_booked_depreciation_date = frappe.db.get_value(
"Depreciation Schedule",
{
"parent": asset_depreciation,
"docstatus": 1,
"journal_entry": ["!=", ""],
},
"schedule_date",
order_by="schedule_date desc",
)
before_purchase_date = add_to_date(asset.purchase_date, days=-1)
future_date = add_to_date(nowdate(), days=1)
if last_booked_depreciation_date:
before_last_booked_depreciation_date = add_to_date(last_booked_depreciation_date, days=-1)
self.assertRaises(frappe.ValidationError, scrap_asset, asset.name, scrap_date=before_purchase_date)
self.assertRaises(frappe.ValidationError, scrap_asset, asset.name, scrap_date=future_date)
self.assertRaises(
frappe.ValidationError, scrap_asset, asset.name, scrap_date=before_last_booked_depreciation_date
)
scrap_asset(asset.name)
asset.load_from_db()
first_asset_depr_schedule.load_from_db()

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,8 +142,8 @@ class AssetMovement(Document):
def update_asset_location_and_custodian(self, asset_id, location, employee):
asset = frappe.get_doc("Asset", asset_id)
if employee and employee != asset.custodian:
frappe.db.set_value("Asset", asset_id, "custodian", employee)
if cstr(employee) != asset.custodian:
frappe.db.set_value("Asset", asset_id, "custodian", cstr(employee))
if location and location != asset.location:
frappe.db.set_value("Asset", asset_id, "location", location)

View File

@@ -268,6 +268,7 @@ def get_asset_depreciation_amount_map(filters, finance_book):
.where(gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account))
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.where(gle.is_opening == "No")
.where(company.name == filters.company)
.where(asset.docstatus == 1)
)
@@ -318,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

@@ -644,9 +644,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

@@ -541,12 +541,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)
@@ -570,7 +566,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
@@ -718,6 +713,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"
@@ -905,18 +901,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"
@@ -930,8 +924,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.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (

View File

@@ -109,21 +109,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

@@ -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

@@ -231,6 +231,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()
# validate inter company transaction rate
@@ -282,11 +287,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()
@@ -2558,6 +2558,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 = {
@@ -2570,6 +2571,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"),
@@ -3061,9 +3073,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):
@@ -3369,14 +3379,27 @@ 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
term_details.base_outstanding = term_details.base_payment_amount
if bill_date:
term_details.due_date = get_due_date(term, bill_date)
@@ -3395,11 +3418,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
@@ -3407,11 +3430,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

@@ -878,6 +878,7 @@ class BuyingController(SubcontractingController):
"asset_category": item_data.get("asset_category"),
"location": row.asset_location,
"company": self.company,
"status": "Draft",
"supplier": self.supplier,
"purchase_date": self.posting_date,
"calculate_depreciation": 0,

View File

@@ -313,7 +313,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
if filters:
if filters.get("customer"):
qb_filter_and_conditions.append(
(proj.customer == filters.get("customer")) | proj.customer.isnull() | proj.customer == ""
(proj.customer == filters.get("customer")) | (proj.customer.isnull()) | (proj.customer == "")
)
if filters.get("company"):

View File

@@ -200,7 +200,11 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
current_stock_qty = args.get(column)
elif args.get("return_qty_from_rejected_warehouse"):
reference_qty = ref.get("rejected_qty") * ref.get("conversion_factor", 1.0)
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
current_stock_qty = (
args.get(column) * args.get("conversion_factor", 1.0)
if column != "stock_qty"
else args.get(column)
)
else:
reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0)
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
@@ -845,13 +849,14 @@ def available_serial_batch_for_return(field, doctype, reference_ids, is_rejected
def get_available_serial_batches(field, doctype, reference_ids, is_rejected=False):
_bundle_ids = get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=is_rejected)
if not _bundle_ids:
return frappe._dict({})
return get_serial_batches_based_on_bundle(field, _bundle_ids)
return get_serial_batches_based_on_bundle(doctype, field, _bundle_ids)
def get_serial_batches_based_on_bundle(field, _bundle_ids):
def get_serial_batches_based_on_bundle(doctype, field, _bundle_ids):
available_dict = frappe._dict({})
batch_serial_nos = frappe.get_all(
"Serial and Batch Bundle",
@@ -863,6 +868,7 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids):
"`tabSerial and Batch Bundle`.`voucher_detail_no`",
"`tabSerial and Batch Bundle`.`voucher_type`",
"`tabSerial and Batch Bundle`.`voucher_no`",
"`tabSerial and Batch Bundle`.`item_code`",
],
filters=[
["Serial and Batch Bundle", "name", "in", _bundle_ids],
@@ -876,6 +882,16 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids):
if frappe.get_cached_value(row.voucher_type, row.voucher_no, "is_return"):
key = frappe.get_cached_value(row.voucher_type + " Item", row.voucher_detail_no, field)
if doctype == "Packed Item":
if key is None:
key = frappe.get_cached_value("Packed Item", row.voucher_detail_no, field)
if row.voucher_type == "Delivery Note":
key = frappe.get_cached_value("Delivery Note Item", key, "dn_detail")
elif row.voucher_type == "Sales Invoice":
key = frappe.get_cached_value("Sales Invoice Item", key, "sales_invoice_item")
key = (row.item_code, key)
if row.voucher_type in ["Sales Invoice", "Delivery Note"]:
row.qty = -1 * row.qty
@@ -904,6 +920,8 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids):
def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False):
filters = {"docstatus": 1, "name": ("in", reference_ids), "serial_and_batch_bundle": ("is", "set")}
if doctype == "Packed Item":
filters = get_filters_for_packed_item(field, reference_ids)
pluck_field = "serial_and_batch_bundle"
if is_rejected:
@@ -917,10 +935,14 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False
pluck=pluck_field,
)
if _bundle_ids and doctype == "Packed Item":
return _bundle_ids
if not _bundle_ids:
return {}
del filters["name"]
if "name" in filters:
del filters["name"]
filters[field] = ("in", reference_ids)
@@ -963,10 +985,29 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False
return _bundle_ids
def get_filters_for_packed_item(field, reference_ids):
names = []
filters = {"docstatus": 1, "dn_detail": ("in", reference_ids)}
if dns := frappe.get_all("Delivery Note Item", filters=filters, pluck="name"):
names.extend(dns)
filters = {"docstatus": 1, "sales_invoice_item": ("in", reference_ids)}
if sis := frappe.get_all("Sales Invoice Item", filters=filters, pluck="name"):
names.extend(sis)
if names:
reference_ids.extend(names)
return {"docstatus": 1, field: ("in", reference_ids), "serial_and_batch_bundle": ("is", "set")}
def filter_serial_batches(parent_doc, data, row, warehouse_field=None, qty_field=None):
if not qty_field:
qty_field = "stock_qty"
if not hasattr(row, qty_field):
qty_field = "qty"
if not warehouse_field:
warehouse_field = "warehouse"
@@ -1056,6 +1097,9 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f
if not qty_field:
qty_field = "stock_qty"
if not hasattr(child_doc, qty_field):
qty_field = "qty"
warehouse = child_doc.get(warehouse_field)
if parent_doc.get("is_internal_customer"):
warehouse = child_doc.get("target_warehouse")

View File

@@ -517,8 +517,15 @@ class SellingController(StockController):
if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
continue
item_details = frappe.get_cached_value(
"Item", d.item_code, ["has_serial_no", "has_batch_no"], as_dict=1
)
if not self.get("return_against") or (
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
get_valuation_method(d.item_code) == "Moving Average"
and self.get("is_return")
and not item_details.has_serial_no
and not item_details.has_batch_no
):
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get("stock_qty") or d.get("actual_qty") or d.get("qty"))
@@ -997,6 +1004,9 @@ def set_default_income_account_for_item(obj):
def get_serial_and_batch_bundle(child, parent, delivery_note_child=None):
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
if parent.get("is_return") and parent.get("packed_items"):
return
if child.get("use_serial_batch_fields"):
return

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)
@@ -335,10 +343,20 @@ class StockController(AccountsController):
return
child_doctype = self.doctype + " Item"
if table_name == "packed_items":
field = "parent_detail_docname"
child_doctype = "Packed Item"
available_dict = available_serial_batch_for_return(field, child_doctype, reference_ids)
for row in self.get(table_name):
if data := available_dict.get(row.get(field)):
value = row.get(field)
if table_name == "packed_items" and row.get("parent_detail_docname"):
value = self.get_value_for_packed_item(row)
if not value:
continue
if data := available_dict.get(value):
data = filter_serial_batches(self, data, row)
bundle = make_serial_batch_bundle_for_return(data, row, self)
row.db_set(
@@ -354,6 +372,14 @@ class StockController(AccountsController):
"incoming_rate", frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate")
)
def get_value_for_packed_item(self, row):
parent_items = self.get("items", {"name": row.parent_detail_docname})
if parent_items:
ref = parent_items[0].get("dn_detail")
return (row.item_code, ref)
return None
def get_reference_ids(self, table_name, qty_field=None, bundle_field=None) -> tuple[str, list[str]]:
field = {
"Sales Invoice": "sales_invoice_item",
@@ -388,6 +414,12 @@ class StockController(AccountsController):
):
reference_ids.append(row.get(field))
if table_name == "packed_items" and row.get("parent_detail_docname"):
parent_rows = self.get("items", {"name": row.parent_detail_docname}) or []
for d in parent_rows:
if d.get(field) and not d.get(bundle_field):
reference_ids.append(d.get(field))
return field, reference_ids
@frappe.request_cache
@@ -1520,7 +1552,9 @@ def get_stock_ledger_preview(doc, filters):
if doc.get("update_stock") or doc.doctype in ("Purchase Receipt", "Delivery Note"):
doc.docstatus = 1
doc.make_bundle_using_old_serial_batch_fields()
doc.update_stock_ledger()
columns = get_sl_columns(filters)
sl_entries = get_sl_entries_for_preview(doc.doctype, doc.name, fields)

View File

@@ -12,6 +12,8 @@ from frappe.utils import cint, flt, get_link_to_form
from erpnext.controllers.stock_controller import StockController
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
get_auto_batch_nos,
get_available_serial_nos,
get_voucher_wise_serial_batch_from_bundle,
)
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -52,9 +54,42 @@ class SubcontractingController(StockController):
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
self.validate_items()
self.create_raw_materials_supplied()
self.set_valuation_rate_for_rm()
else:
super().validate()
def set_valuation_rate_for_rm(self):
rate_changed = False
if self.doctype == "Subcontracting Receipt":
for row in self.supplied_items:
kwargs = frappe._dict(
{
"item_code": row.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": flt(row.consumed_qty) * (-1 if not self.is_return else 1),
"voucher_type": self.doctype,
"voucher_no": self.name,
"company": self.company,
"serial_and_batch_bundle": row.serial_and_batch_bundle,
"voucher_detail_no": row.name,
"batch_no": row.batch_no,
"serial_no": row.serial_no,
"use_serial_batch_fields": row.use_serial_batch_fields,
}
)
rate = get_incoming_rate(kwargs)
precision = frappe.get_precision("Subcontracting Receipt Supplied Item", "rate")
if flt(rate, precision) != flt(row.rate, precision):
row.rate = rate
row.amount = flt(row.consumed_qty) * flt(rate)
rate_changed = True
if rate_changed:
self.calculate_items_qty_and_amount()
def validate_rejected_warehouse(self):
for item in self.get("items"):
if flt(item.rejected_qty) and not item.rejected_warehouse:
@@ -166,6 +201,9 @@ class SubcontractingController(StockController):
self.set(self.raw_material_table, [])
return
if not self.get(self.raw_material_table):
return
item_dict = self.__get_data_before_save()
if not item_dict:
return True
@@ -599,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:
@@ -610,6 +649,67 @@ class SubcontractingController(StockController):
self.set_rate_for_supplied_items(rm_obj, item_row)
elif self.backflush_based_on == "BOM":
self.update_rate_for_supplied_items()
self.set_batch_for_supplied_items()
def set_batch_for_supplied_items(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos_for_outward
from erpnext.stock.get_item_details import get_filtered_serial_nos
if self.is_return:
return
for row in self.supplied_items:
item_details = frappe.get_cached_value(
"Item", row.rm_item_code, ["has_batch_no", "has_serial_no"], as_dict=1
)
if not item_details.has_batch_no and not item_details.has_serial_no:
continue
if not row.use_serial_batch_fields:
continue
kwargs = frappe._dict(
{
"item_code": row.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": flt(row.consumed_qty),
}
)
if item_details.has_serial_no and not row.serial_and_batch_bundle and not row.serial_no:
serial_nos = get_available_serial_nos(kwargs)
if serial_nos:
serial_nos = [sn.get("serial_no") for sn in serial_nos]
serial_nos = get_filtered_serial_nos(serial_nos, self, "supplied_items")
row.serial_no = "\n".join(serial_nos)
elif item_details.has_batch_no and not row.serial_and_batch_bundle and not row.batch_no:
batches = get_auto_batch_nos(kwargs)
if batches:
consumed_qty = row.consumed_qty
for index, d in enumerate(batches):
if consumed_qty <= 0:
break
if index == 0:
row.batch_no = d.get("batch_no")
row.consumed_qty = d.get("qty")
consumed_qty -= d.get("qty")
else:
new_row = self.append("supplied_items", {})
new_row.update(frappe.copy_doc(row).as_dict())
new_row.update(
{
"consumed_qty": d.get("qty"),
"batch_no": d.get("batch_no"),
"rate": row.rate,
"amount": flt(d.get("qty")) * flt(row.rate),
}
)
consumed_qty -= d.get("qty")
def update_rate_for_supplied_items(self):
if self.doctype != "Subcontracting Receipt":

View File

@@ -1308,6 +1308,7 @@ def make_subcontracted_items():
"Subcontracted Item SA7": {},
"Subcontracted Item SA8": {},
"Subcontracted Item SA9": {"stock_uom": "Litre"},
"Subcontracted Item SA10": {},
}
for item, properties in sub_contracted_items.items():
@@ -1329,6 +1330,7 @@ def make_raw_materials():
"Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRIID.####"},
"Subcontracted SRM Item 8": {},
"Subcontracted SRM Item 9": {"stock_uom": "Litre"},
"Subcontracted SRM Item 10": {},
}
for item, properties in raw_materials.items():
@@ -1357,6 +1359,7 @@ def make_service_items():
"Subcontracted Service Item 7": {},
"Subcontracted Service Item 8": {},
"Subcontracted Service Item 9": {},
"Subcontracted Service Item 10": {},
}
for item, properties in service_items.items():
@@ -1381,6 +1384,7 @@ def make_bom_for_subcontracted_items():
"Subcontracted Item SA6": ["Subcontracted SRM Item 3"],
"Subcontracted Item SA7": ["Subcontracted SRM Item 1"],
"Subcontracted Item SA8": ["Subcontracted SRM Item 8"],
"Subcontracted Item SA10": ["Subcontracted SRM Item 10"],
}
for item_code, raw_materials in boms.items():

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

@@ -253,6 +253,13 @@ class BOMCreator(Document):
if not row.fg_reference_id and production_item_wise_rm.get((row.fg_item, row.fg_reference_id)):
frappe.throw(_("Please set Parent Row No for item {0}").format(row.fg_item))
key = (row.fg_item, row.fg_reference_id)
if key not in production_item_wise_rm:
production_item_wise_rm.setdefault(
key,
frappe._dict({"items": [], "bom_no": "", "fg_item_data": row}),
)
production_item_wise_rm[(row.fg_item, row.fg_reference_id)]["items"].append(row)
reverse_tree = OrderedDict(reversed(list(production_item_wise_rm.items())))

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

@@ -724,6 +724,9 @@ class ProductionPlan(Document):
if not wo_list:
frappe.msgprint(_("No Work Orders were created"))
if not po_list:
frappe.msgprint(_("No Purchase Orders were created"))
def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
items_data = self.get_production_items()
@@ -748,6 +751,7 @@ class ProductionPlan(Document):
work_order_data = {
"wip_warehouse": default_warehouses.get("wip_warehouse"),
"fg_warehouse": default_warehouses.get("fg_warehouse"),
"scrap_warehouse": default_warehouses.get("scrap_warehouse"),
"company": self.get("company"),
}
@@ -1542,6 +1546,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"):
@@ -1575,7 +1580,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,
@@ -1631,7 +1636,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
}
)
sales_order = doc.get("sales_order")
sales_order = data.get("sales_order")
for item_code, details in item_details.items():
so_item_details.setdefault(sales_order, frappe._dict())
@@ -1821,7 +1826,7 @@ def get_sub_assembly_items(
def set_default_warehouses(row, default_warehouses):
for field in ["wip_warehouse", "fg_warehouse"]:
for field in ["wip_warehouse", "fg_warehouse", "scrap_warehouse"]:
if not row.get(field):
row[field] = default_warehouses.get(field)
@@ -1838,7 +1843,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)
@@ -1954,6 +1959,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)
@@ -1991,6 +1997,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

@@ -2828,6 +2828,111 @@ class TestWorkOrder(FrappeTestCase):
wo.operations[3].planned_start_time, add_to_date(wo.operations[1].planned_end_time, minutes=10)
)
def test_req_qty_clamping_in_manufacture_entry(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import (
make_stock_entry as make_stock_entry_test_record,
)
fg_item = "Test Unconsumed RM FG Item"
rm_item_1 = "Test Unconsumed RM Item 1"
rm_item_2 = "Test Unconsumed RM Item 2"
source_warehouse = "_Test Warehouse - _TC"
wip_warehouse = "Stores - _TC"
fg_warehouse = create_warehouse("_Test Finished Goods Warehouse", company="_Test Company")
make_item(fg_item, {"is_stock_item": 1})
make_item(rm_item_1, {"is_stock_item": 1})
make_item(rm_item_2, {"is_stock_item": 1})
# create a BOM: 1 FG = 1 RM1 + 1 RM2
bom = make_bom(
item=fg_item,
source_warehouse=source_warehouse,
raw_materials=[rm_item_1, rm_item_2],
operating_cost_per_bom_quantity=1,
do_not_submit=True,
)
for row in bom.exploded_items:
make_stock_entry_test_record(
item_code=row.item_code,
target=source_warehouse,
qty=100,
basic_rate=100,
)
wo = make_wo_order_test_record(
item=fg_item,
qty=50,
source_warehouse=source_warehouse,
wip_warehouse=wip_warehouse,
)
wo.submit()
# first partial transfer & manufacture (6 units)
se_transfer_1 = frappe.get_doc(
make_stock_entry(wo.name, "Material Transfer for Manufacture", 6, wip_warehouse)
)
se_transfer_1.insert()
se_transfer_1.submit()
stock_entry_1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 6, fg_warehouse))
# remove rm_2 from the items to simulate unconsumed RM scenario
stock_entry_1.items = [row for row in stock_entry_1.items if row.item_code != rm_item_2]
stock_entry_1.save()
stock_entry_1.submit()
wo.reload()
se_transfer_2 = frappe.get_doc(
make_stock_entry(wo.name, "Material Transfer for Manufacture", 20, wip_warehouse)
)
se_transfer_2.insert()
se_transfer_2.submit()
stock_entry_2 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 20, fg_warehouse))
# validate rm_item_2 quantity is clamped correctly (per-unit BOM = 1 → max 20)
for row in stock_entry_2.items:
if row.item_code == rm_item_2:
self.assertLessEqual(row.qty, 20)
self.assertGreaterEqual(row.qty, 0)
def test_overproduction_allowed_qty(self):
"""Test overproduction allowed qty in work order"""
allow_overproduction("overproduction_percentage_for_work_order", 50)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=10)
test_stock_entry.make_stock_entry(
item_code="_Test Item", target="Stores - _TC", qty=100, basic_rate=100
)
test_stock_entry.make_stock_entry(
item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC",
qty=100,
basic_rate=1000.0,
)
mt_stock_entry = frappe.get_doc(
make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 10)
)
mt_stock_entry.submit()
fg_stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
fg_stock_entry.items[2].qty = 15
fg_stock_entry.fg_completed_qty = 15
fg_stock_entry.submit()
wo_order.reload()
self.assertEqual(wo_order.produced_qty, 15)
self.assertEqual(wo_order.status, "Completed")
allow_overproduction("overproduction_percentage_for_work_order", 0)
def make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import (

View File

@@ -26,7 +26,6 @@ def get_exploded_items(bom, data, indent=0, qty=1):
)
for item in exploded_items:
print(item.bom_no, indent)
item["indent"] = indent
data.append(
{

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

@@ -263,6 +263,7 @@ execute:frappe.rename_doc("Report", "TDS Payable Monthly", "Tax Withholding Deta
erpnext.patches.v14_0.update_proprietorship_to_individual
erpnext.patches.v15_0.rename_subcontracting_fields
erpnext.patches.v15_0.unset_incorrect_additional_discount_percentage
erpnext.patches.v16_0.create_company_custom_fields
[post_model_sync]
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
@@ -421,3 +422,4 @@ execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_fo
erpnext.patches.v15_0.add_company_payment_gateway_account
erpnext.patches.v15_0.update_uae_zero_rated_fetch
erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter
erpnext.patches.v15_0.set_asset_status_if_not_already_set

View File

@@ -9,5 +9,9 @@ def execute():
docs = frappe.get_all(
"Material Request", filters={"buying_price_list": ["is", "not set"], "docstatus": 1}, pluck="name"
)
for doc in docs:
frappe.db.set_value("Material Request", doc, "buying_price_list", default_buying_price_list)
frappe.db.auto_commit_on_many_writes = 1
try:
for doc in docs:
frappe.db.set_value("Material Request", doc, "buying_price_list", default_buying_price_list)
finally:
frappe.db.auto_commit_on_many_writes = 0

View File

@@ -0,0 +1,13 @@
import frappe
from frappe.query_builder import DocType
def execute():
Asset = DocType("Asset")
query = (
frappe.qb.update(Asset)
.set(Asset.status, "Draft")
.where((Asset.docstatus == 0) & ((Asset.status.isnull()) | (Asset.status == "")))
)
query.run()

View File

@@ -0,0 +1,6 @@
from erpnext.setup.install import create_custom_company_links
def execute():
"""Add link fields to Company in Email Account and Communication."""
create_custom_company_links()

View File

@@ -245,6 +245,7 @@
"fieldname": "act_start_date",
"fieldtype": "Date",
"label": "Actual Start Date (via Timesheet)",
"no_copy": 1,
"oldfieldname": "act_start_date",
"oldfieldtype": "Date",
"read_only": 1
@@ -253,6 +254,7 @@
"fieldname": "actual_time",
"fieldtype": "Float",
"label": "Actual Time in Hours (via Timesheet)",
"no_copy": 1,
"read_only": 1
},
{
@@ -263,6 +265,7 @@
"fieldname": "act_end_date",
"fieldtype": "Date",
"label": "Actual End Date (via Timesheet)",
"no_copy": 1,
"oldfieldname": "act_end_date",
"oldfieldtype": "Date",
"read_only": 1
@@ -277,6 +280,7 @@
"fieldname": "total_costing_amount",
"fieldtype": "Currency",
"label": "Total Costing Amount (via Timesheet)",
"no_copy": 1,
"oldfieldname": "actual_budget",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
@@ -290,6 +294,7 @@
"fieldname": "total_billing_amount",
"fieldtype": "Currency",
"label": "Total Billable Amount (via Timesheet)",
"no_copy": 1,
"read_only": 1
},
{
@@ -399,7 +404,7 @@
"is_tree": 1,
"links": [],
"max_attachments": 5,
"modified": "2024-01-08 16:00:41.296203",
"modified": "2025-10-16 08:39:12.214577",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
@@ -429,4 +434,4 @@
"timeline_field": "project",
"title_field": "subject",
"track_seen": 1
}
}

View File

@@ -3,6 +3,12 @@
frappe.query_reports["Delayed Tasks Summary"] = {
filters: [
{
fieldname: "project",
label: __("Project"),
fieldtype: "Link",
options: "Project",
},
{
fieldname: "from_date",
label: __("From Date"),

View File

@@ -23,6 +23,7 @@ def get_data(filters):
fields=[
"name",
"subject",
"project",
"exp_start_date",
"exp_end_date",
"status",
@@ -56,7 +57,7 @@ def get_data(filters):
def get_conditions(filters):
conditions = frappe._dict()
keys = ["priority", "status"]
keys = ["priority", "status", "project"]
for key in keys:
if filters.get(key):
conditions[key] = filters.get(key)
@@ -89,6 +90,13 @@ def get_columns():
columns = [
{"fieldname": "name", "fieldtype": "Link", "label": _("Task"), "options": "Task", "width": 150},
{"fieldname": "subject", "fieldtype": "Data", "label": _("Subject"), "width": 200},
{
"fieldname": "project",
"fieldtype": "Link",
"label": _("Project"),
"options": "Project",
"width": 150,
},
{"fieldname": "status", "fieldtype": "Data", "label": _("Status"), "width": 100},
{"fieldname": "priority", "fieldtype": "Data", "label": _("Priority"), "width": 80},
{"fieldname": "progress", "fieldtype": "Data", "label": _("Progress (%)"), "width": 120},

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

@@ -237,10 +237,10 @@ erpnext.accounts.pos = {
frappe.ui.form.on(doctype, {
mode_of_payment: function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
get_payment_mode_account(frm, d.mode_of_payment, function(account){
frappe.model.set_value(cdt, cdn, 'account', account)
})
}
erpnext.accounts.pos.get_payment_mode_account(frm, d.mode_of_payment, function (account) {
frappe.model.set_value(cdt, cdn, "account", account);
});
},
});
},

View File

@@ -17,7 +17,17 @@ erpnext.buying = {
this.setup_queries(doc, cdt, cdn);
super.onload();
this.frm.set_query('shipping_rule', function() {
if (["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doctype)) {
this.frm.set_query("supplier", function () {
return {
filters: {
is_transporter: 0,
},
};
});
}
this.frm.set_query("shipping_rule", function () {
return {
filters: {
"shipping_rule_type": "Buying"
@@ -171,9 +181,14 @@ 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;
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 || "");
},
});

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

@@ -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);
}
@@ -1082,12 +1083,25 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
}
due_date(doc, cdt) {
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(doc, cdt, cdn) {
// 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
if (doc.doctype !== cdt) {
// triggered by change to the due_date field in payment schedule child table
// do nothing to avoid infinite clearing loop
// Remove fields as due_date is auto-managed by payment terms
const row = locals[cdt][cdn];
["due_date_based_on", "credit_days", "credit_months"].forEach((field) => {
row[field] = "";
});
this.frm.refresh_field("payment_schedule");
return;
}
@@ -2137,16 +2151,22 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
this.frm.doc.name).options,
"master_name": this.frm.doc.taxes_and_charges
},
callback: function(r) {
if(!r.exc) {
if(me.frm.doc.shipping_rule && me.frm.doc.taxes) {
for (let tax of r.message) {
callback: function (r) {
if (!r.exc) {
let taxes = r.message;
taxes.forEach((tax) => {
if (me.frm.doc?.cost_center && !tax.cost_center) {
tax.cost_center = me.frm.doc.cost_center;
}
});
if (me.frm.doc.shipping_rule && me.frm.doc.taxes) {
for (let tax of taxes) {
me.frm.add_child("taxes", tax);
}
refresh_field("taxes");
} else {
me.frm.set_value("taxes", r.message);
me.frm.set_value("taxes", taxes);
me.calculate_taxes_and_totals();
}
}
@@ -2573,6 +2593,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
'valid_from': ["<=", doc.transaction_date || doc.bill_date || doc.posting_date],
'item_group': item.item_group,
"base_net_rate": item.base_net_rate,
"disabled": 0,
}
if (doc.tax_category)
@@ -2614,6 +2635,17 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
payment_term(doc, cdt, cdn) {
const me = this;
var row = locals[cdt][cdn];
// 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",
@@ -2626,14 +2658,17 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
},
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);
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

@@ -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

@@ -443,3 +443,14 @@ def create_internal_customer(customer_name=None, represents_company=None, allowe
customer_name = frappe.db.get_value("Customer", customer_name)
return customer_name
def make_customer(customer_name):
if not frappe.db.exists("Customer", customer_name):
customer = frappe.new_doc("Customer")
customer.customer_name = customer_name
customer.customer_type = "Individual"
customer.insert()
return customer.name
else:
return customer_name

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

@@ -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 InvalidQtyError, update_child_qty_rate
from erpnext.controllers.accounts_controller import InvalidQtyError, get_due_date, update_child_qty_rate
from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import (
make_maintenance_schedule,
)
@@ -1680,14 +1680,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"
@@ -1701,8 +1700,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
@@ -2421,16 +2418,14 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
self.assertEqual(si2.items[0].qty, 20)
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

@@ -17,6 +17,7 @@
"role_to_override_stop_action",
"column_break_15",
"maintain_same_sales_rate",
"fallback_to_default_price_list",
"editable_price_list_rate",
"validate_selling_price",
"editable_bundle_item_rates",
@@ -216,6 +217,12 @@
"fieldname": "allow_zero_qty_in_quotation",
"fieldtype": "Check",
"label": "Allow Quotation with Zero Quantity"
},
{
"default": "0",
"fieldname": "fallback_to_default_price_list",
"fieldtype": "Check",
"label": "Use Prices from Default Price List as Fallback"
}
],
"grid_page_length": 50,
@@ -224,7 +231,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-05-06 15:23:14.332971",
"modified": "2025-09-23 21:10:14.826653",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",

View File

@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.model.document import Document
from frappe.utils import cint
@@ -33,6 +34,7 @@ class SellingSettings(Document):
editable_bundle_item_rates: DF.Check
editable_price_list_rate: DF.Check
enable_discount_accounting: DF.Check
fallback_to_default_price_list: DF.Check
hide_tax_id: DF.Check
maintain_same_rate_action: DF.Literal["Stop", "Warn"]
maintain_same_sales_rate: DF.Check
@@ -69,16 +71,35 @@ class SellingSettings(Document):
hide_name_field=False,
)
self.validate_fallback_to_default_price_list()
def validate_fallback_to_default_price_list(self):
if (
self.fallback_to_default_price_list
and self.has_value_changed("fallback_to_default_price_list")
and frappe.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")
):
stock_meta = frappe.get_meta("Stock Settings")
frappe.msgprint(
_(
"You have enabled {0} and {1} in {2}. This can lead to prices from the default price list being inserted into the transaction price list."
).format(
"<i>{}</i>".format(_(self.meta.get_label("fallback_to_default_price_list"))),
"<i>{}</i>".format(_(stock_meta.get_label("auto_insert_price_list_rate_if_missing"))),
frappe.bold(_("Stock Settings")),
)
)
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

@@ -394,6 +394,10 @@ erpnext.PointOfSale.Payment = class {
const payments = doc.payments;
const currency = doc.currency;
if (!this.$payment_modes.is(":visible")) {
return;
}
this.$payment_modes.html(
`${payments
.map((p, i) => {
@@ -612,6 +616,10 @@ erpnext.PointOfSale.Payment = class {
const currency = doc.currency;
const label = __("Change Amount");
if (!this.$totals.is(":visible")) {
return;
}
this.$totals.html(
`<div class="col">
<div class="total-label">${__("Grand Total")}</div>

View File

@@ -763,13 +763,11 @@ def update_company_current_month_sales(company):
def update_company_monthly_sales(company):
"""Cache past year monthly sales of every company based on sales invoices"""
import json
from frappe.utils.goal import get_monthly_results
filter_str = f"company = {frappe.db.escape(company)} and status != 'Draft' and docstatus=1"
filter_dict = {"company": company, "status": ["!=", "Draft"], "docstatus": 1}
month_to_value_dict = get_monthly_results(
"Sales Invoice", "base_grand_total", "posting_date", filter_str, "sum"
"Sales Invoice", "base_grand_total", "posting_date", filter_dict, "sum"
)
frappe.db.set_value("Company", company, "sales_monthly_history", json.dumps(month_to_value_dict))

View File

@@ -24,6 +24,7 @@ frappe.ui.form.on("Driver", {
transporter: function (frm, cdt, cdn) {
// this assumes that supplier's address has same title as supplier's name
if (!frm.doc.transporter) return;
frappe.db
.get_doc("Address", null, { address_title: frm.doc.transporter })
.then((r) => {

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