Compare commits

..

278 Commits

Author SHA1 Message Date
Frappe PR Bot
6490b7d561 chore(release): Bumped to Version 13.52.2
## [13.52.2](https://github.com/frappe/erpnext/compare/v13.52.1...v13.52.2) (2023-07-08)

### Bug Fixes

* gst_hsn_code is ambiguous on gst reports ([2893ae7](2893ae72f5))
2023-07-08 13:17:58 +00:00
ruthra kumar
a4d8f9cb94 Merge pull request #36031 from frappe/mergify/bp/version-13/pr-36028
fix: gst_hsn_code is ambiguous on gst reports (backport #36028)
2023-07-08 18:46:34 +05:30
ruthra kumar
2893ae72f5 fix: gst_hsn_code is ambiguous on gst reports
(cherry picked from commit 3a00052b49)
2023-07-08 12:38:53 +00:00
Frappe PR Bot
1116cee831 chore(release): Bumped to Version 13.52.1
## [13.52.1](https://github.com/frappe/erpnext/compare/v13.52.0...v13.52.1) (2023-07-05)

### Bug Fixes

* **Payment Entry:** compare rounded amount ([#36011](https://github.com/frappe/erpnext/issues/36011)) ([a7d26b0](a7d26b0c20))
* **Payment Entry:** compare rounded amount ([#36011](https://github.com/frappe/erpnext/issues/36011)) ([a370dc3](a370dc3dcc))
2023-07-05 16:23:25 +00:00
Deepesh Garg
a7d26b0c20 fix(Payment Entry): compare rounded amount (#36011)
fix(Payment Entry): compare rounded amount (#36011)
2023-07-05 21:47:04 +05:30
mergify[bot]
a370dc3dcc fix(Payment Entry): compare rounded amount (#36011)
fix(Payment Entry): compare rounded amount (#36011)

(cherry picked from commit 4badac8e9e)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
(cherry picked from commit b04c190e33)
2023-07-05 16:09:00 +00:00
Frappe PR Bot
77d019cc3b chore(release): Bumped to Version 13.52.0
# [13.52.0](https://github.com/frappe/erpnext/compare/v13.51.7...v13.52.0) (2023-07-05)

### Bug Fixes

* conflicts ([b9833db](b9833db7bd))
* Expense Account filter in Sales Invoice ([#35944](https://github.com/frappe/erpnext/issues/35944)) ([b63fbe4](b63fbe4286))
* Further sort purchase_order_analysis to get consistent response ([0ef0ff4](0ef0ff470f))
* reposting has not changed valuation rate ([d4e680c](d4e680c109))
* Update no copy for received_qty field ([#35965](https://github.com/frappe/erpnext/issues/35965)) ([10c9640](10c9640cbd))

### Features

* **DATEV:** against account for opening entries ([#35941](https://github.com/frappe/erpnext/issues/35941)) ([0602ddc](0602ddcfc8))
2023-07-05 09:49:20 +00:00
Deepesh Garg
fd84119273 Merge pull request #35997 from frappe/version-13-hotfix
chore: release v13
2023-07-05 15:17:37 +05:30
Suraj Shetty
9b9d839835 Merge pull request #35986 from frappe/fix-report-sorting 2023-07-04 11:48:28 +05:30
Suraj Shetty
0ef0ff470f fix: Further sort purchase_order_analysis to get consistent response 2023-07-04 11:09:38 +05:30
mergify[bot]
10c9640cbd fix: Update no copy for received_qty field (#35965)
* fix: Update no copy for received_qty field (#35965)

(cherry picked from commit 5448859254)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
#	erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json

* chore: resolve conflicts

* chore: resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-07-03 16:38:26 +05:30
Raffael Meyer
da1218f324 chore: bump pandas (#35939) 2023-07-03 14:04:22 +05:30
rohitwaghchaure
407e5b5fa3 Merge pull request #35970 from frappe/mergify/bp/version-13-hotfix/pr-35955
fix: incorrect reposting causing stock adjustment entry (backport #35955)
2023-07-03 11:08:20 +05:30
rohitwaghchaure
b9833db7bd fix: conflicts 2023-07-03 09:53:16 +05:30
Rohit Waghchaure
d4e680c109 fix: reposting has not changed valuation rate
(cherry picked from commit c0c693d8b0)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
2023-07-03 04:21:32 +00:00
mergify[bot]
b63fbe4286 fix: Expense Account filter in Sales Invoice (#35944)
fix: Expense Account filter in Sales Invoice (#35944)

(cherry picked from commit d54f52474a)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-07-01 18:14:17 +05:30
Raffael Meyer
0602ddcfc8 feat(DATEV): against account for opening entries (#35941)
* fix: add missing german translations

* feat(DATEV): against account for opening entries

Allow to specify a separate temporary against account
for opening entries

* feat(DATEV Settings): validate account no. length

* style: format with black

* style: format with black

* test: new settings field, rename filter
2023-06-30 20:02:12 +05:30
mergify[bot]
18c3a668d9 chore: update translations (#35905)
chore: update translations

chore: update translations
(cherry picked from commit 1d1103f39c)

Co-authored-by: RJPvT <48353029+RJPvT@users.noreply.github.com>
2023-06-29 13:29:56 +05:30
Frappe PR Bot
26489121f3 chore(release): Bumped to Version 13.51.7
## [13.51.7](https://github.com/frappe/erpnext/compare/v13.51.6...v13.51.7) (2023-06-28)

### Bug Fixes

* asset movement (backport [#35918](https://github.com/frappe/erpnext/issues/35918)) ([#35924](https://github.com/frappe/erpnext/issues/35924)) ([0bcd047](0bcd0476a2))
* employee link fields in payroll reports ([#619](https://github.com/frappe/erpnext/issues/619)) ([#35845](https://github.com/frappe/erpnext/issues/35845)) ([6c4dff3](6c4dff38da))
* filter parent warehouses not showing (backport [#35897](https://github.com/frappe/erpnext/issues/35897)) ([#35900](https://github.com/frappe/erpnext/issues/35900)) ([bcfd770](bcfd7708f2))
* frappe.exceptions.DoesNotExistError: DocType KSA VAT Setting ([#35797](https://github.com/frappe/erpnext/issues/35797)) ([3785fe6](3785fe6927)), closes [#35795](https://github.com/frappe/erpnext/issues/35795)
* show non-depreciable assets in fixed asset register (backport [#35858](https://github.com/frappe/erpnext/issues/35858)) ([#35861](https://github.com/frappe/erpnext/issues/35861)) ([2e2c319](2e2c319f20))
* TDS amount calculation post LDC breach ([#35886](https://github.com/frappe/erpnext/issues/35886)) ([4dd088c](4dd088cba4))
* use correct fieldname for purchase receipt column in item_wise_purchase_register report (backport [#35828](https://github.com/frappe/erpnext/issues/35828)) ([#35848](https://github.com/frappe/erpnext/issues/35848)) ([de529f0](de529f0adf))
* **ux:** PO Get Items From Open Material Requests (backport [#35894](https://github.com/frappe/erpnext/issues/35894)) ([#35896](https://github.com/frappe/erpnext/issues/35896)) ([12b6257](12b62571b8))

### Performance Improvements

* improve item wise register reports (backport [#35908](https://github.com/frappe/erpnext/issues/35908)) ([#35912](https://github.com/frappe/erpnext/issues/35912)) ([4134459](41344593c9))
2023-06-28 16:08:20 +00:00
Deepesh Garg
41902c3676 Merge pull request #35902 from frappe/version-13-hotfix
chore: release v13
2023-06-28 21:33:23 +05:30
mergify[bot]
0bcd0476a2 fix: asset movement (backport #35918) (#35924)
* fix: asset movement (#35918)

fix: asset movement fixes
(cherry picked from commit e16c14863b)

# Conflicts:
#	erpnext/assets/doctype/asset_movement/asset_movement.py

* chore: fix conflict

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-28 21:00:02 +05:30
mergify[bot]
41344593c9 perf: improve item wise register reports (backport #35908) (#35912)
perf: improve item wise register reports (#35908)

(cherry picked from commit 33ee01174b)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-28 10:25:33 +05:30
mergify[bot]
bcfd7708f2 fix: filter parent warehouses not showing (backport #35897) (#35900)
* fix: filter parent warehouses not showing (#35897)

(cherry picked from commit af418d2342)

* chore: add company filter for parent warehouse

---------

Co-authored-by: HLD <hanglaoda@hotmail.com>
Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-27 14:27:33 +05:30
mergify[bot]
4dd088cba4 fix: TDS amount calculation post LDC breach (#35886)
* fix: TDS amount calculation post LDC breach

(cherry picked from commit 1f9ef6c48f)

# Conflicts:
#	erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py

* chore: resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-27 13:12:06 +05:30
mergify[bot]
12b62571b8 fix(ux): PO Get Items From Open Material Requests (backport #35894) (#35896)
fix(ux): PO Get Items From Open Material Requests

(cherry picked from commit 3a00bf83d6)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-27 12:30:08 +05:30
Ashish Shah
3785fe6927 fix: frappe.exceptions.DoesNotExistError: DocType KSA VAT Setting (#35797)
fix: frappe.exceptions.DoesNotExistError: DocType KSA VAT Setting not found

When migrating from v12->v13 migration fails
While migrating the doctype is not reloaded before adding permission. #35795
2023-06-24 16:40:12 +05:30
mergify[bot]
de529f0adf fix: use correct fieldname for purchase receipt column in item_wise_purchase_register report (backport #35828) (#35848)
fix: use correct fieldname for purchase receipt column in item_wise_purcchase_register report

(cherry picked from commit dcfc86e3af)

Co-authored-by: phot0n <ritwikpuri5678@gmail.com>
2023-06-23 11:37:38 +05:30
mergify[bot]
2e2c319f20 fix: show non-depreciable assets in fixed asset register (backport #35858) (#35861)
fix: show non-depreciable assets in fixed asset register (#35858)

fix: show non-depr assets in fixed asset register
(cherry picked from commit 42d09448ee)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-23 08:25:11 +05:30
mergify[bot]
8939e95c62 chore: asset scrap and restore fixes [v14] (backport #35851) (#35854)
* chore: asset scrap and restore fixes [v14] (#35851)

chore: better err msg on cancelling JE for asset scrap and allow restoring non-depr assets
(cherry picked from commit 69780da099)

# Conflicts:
#	erpnext/assets/doctype/asset/depreciation.py

* chore: fix conflict

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-22 23:01:59 +05:30
Rucha Mahabal
6c4dff38da fix: employee link fields in payroll reports (#619) (#35845) 2023-06-22 18:10:54 +05:30
saeedkola
000ebe4479 Fixes issue of asset value_after_depreciation field getting updated twice if workflow is enabled in Journal Entry (#35821)
* Fixes issue of asset value_after_depreciation field getting updated twice if workflow is enabled in Journal Entry

* chore: remove unnecessary line break

* chore: formatting

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-21 14:42:16 +05:30
Daizy Modi
0fe95bf77e chore: remove whitelisting for methods not used from UI (#35592) 2023-06-21 10:33:28 +05:30
Frappe PR Bot
60a170d1a4 chore(release): Bumped to Version 13.51.6
## [13.51.6](https://github.com/frappe/erpnext/compare/v13.51.5...v13.51.6) (2023-06-21)

### Bug Fixes

* account group totals calculation to consider include_in_gross ([f22969d](f22969d266))
* add total col for gross and net profit ([e899c30](e899c30428))
* add validation for QI in PR (backport [#35677](https://github.com/frappe/erpnext/issues/35677)) ([#35758](https://github.com/frappe/erpnext/issues/35758)) ([0a8b714](0a8b7148a5))
* Allocated amount validation for other party types ([#35741](https://github.com/frappe/erpnext/issues/35741)) ([3d0add8](3d0add81fa))
* date and finance book fixes in fixed asset register (backport [#35751](https://github.com/frappe/erpnext/issues/35751)) ([#35800](https://github.com/frappe/erpnext/issues/35800)) ([aa8446d](aa8446d794))
* don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (backport [#35714](https://github.com/frappe/erpnext/issues/35714)) ([#35716](https://github.com/frappe/erpnext/issues/35716)) ([0e11317](0e11317303))
* fix get outstanding invoices btn and add get outstanding orders btn (backport [#35776](https://github.com/frappe/erpnext/issues/35776)) ([#35788](https://github.com/frappe/erpnext/issues/35788)) ([04990d5](04990d51db))
* Incorrect field while calculating Tax withholding net total ([571c977](571c977e8e))
* Incorrect field while calculating Tax withholding net total ([b95d459](b95d459812))
* loan interest accrual date ([#35695](https://github.com/frappe/erpnext/issues/35695)) ([46d0b7d](46d0b7d317))

### Performance Improvements

* index `purpose` in `Stock Entry` (backport [#35782](https://github.com/frappe/erpnext/issues/35782)) ([#35784](https://github.com/frappe/erpnext/issues/35784)) ([7239e83](7239e839a0))
2023-06-21 02:06:13 +00:00
Deepesh Garg
51dd0ec876 Merge pull request #35806 from frappe/version-13-hotfix
chore: release v13
2023-06-21 07:34:34 +05:30
mergify[bot]
aa8446d794 fix: date and finance book fixes in fixed asset register (backport #35751) (#35800)
* fix: date and finance book fixes in fixed asset register (#35751)

* fix: handle finance books properly and show all assets by default in fixed asset register

* chore: rename value to depr amount

* chore: get asset value for correct fb properly

* chore: rename include_default_book_entries to include_default_book_assets

(cherry picked from commit 0d12588583)

# Conflicts:
#	erpnext/assets/report/fixed_asset_register/fixed_asset_register.py

* chore: resolving conflicts and renaming entries to assets

* chore: formatting

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-20 13:03:06 +05:30
mergify[bot]
7239e839a0 perf: index purpose in Stock Entry (backport #35782) (#35784)
* perf: index `purpose` in `Stock Entry`

(cherry picked from commit 4f941ac5c0)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.json

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-20 12:02:04 +05:30
mergify[bot]
04990d51db fix: fix get outstanding invoices btn and add get outstanding orders btn (backport #35776) (#35788)
* fix: fix get outstanding invoices btn and add get outstanding orders btn (#35776)

* fix: fix get outstanding invoices btn and add get outstanding orders btn

* chore: remove unnecessary arg

(cherry picked from commit c1da3ddbbf)

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

* chore: resolving conflicts

* chore: resolving conflicts properly

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-19 20:42:28 +05:30
Frappe PR Bot
0ec74b059e chore(release): Bumped to Version 13.51.5
## [13.51.5](https://github.com/frappe/erpnext/compare/v13.51.4...v13.51.5) (2023-06-19)

### Bug Fixes

* Allocated amount validation for other party types ([#35741](https://github.com/frappe/erpnext/issues/35741)) ([75b3423](75b3423ab1))
2023-06-19 11:38:29 +00:00
Deepesh Garg
2a6e80214c Merge pull request #35775 from frappe/mergify/bp/version-13/pr-35771
fix: Allocated amount validation for other party types (#35741)
2023-06-19 17:06:48 +05:30
ruthra kumar
af10d8080b Merge pull request #35780 from frappe/mergify/bp/version-13-hotfix/pr-35320
fix: Gross and Net Profit Report - incorrect calculation of totals (backport #35320)
2023-06-19 16:48:17 +05:30
Anoop Kurungadam
e899c30428 fix: add total col for gross and net profit
(cherry picked from commit cb9b4fbb91)
2023-06-19 10:26:00 +00:00
Anoop Kurungadam
a53832e16e refactor: merge separate loops for calculating group / leaf node totals
rename function
remove return statement as the list is  mutated

(cherry picked from commit 1a3b9c5bdf)
2023-06-19 10:26:00 +00:00
Anoop Kurungadam
c6885e6789 refactor: remove unused parameters
(cherry picked from commit 50822f207e)
2023-06-19 10:26:00 +00:00
Anoop Kurungadam
f22969d266 fix: account group totals calculation to consider include_in_gross
(cherry picked from commit 8dcb9302b4)
2023-06-19 10:26:00 +00:00
mergify[bot]
75b3423ab1 fix: Allocated amount validation for other party types (#35741)
* fix: Allocated amount validation for other party types (#35741)

* fix: Allocated amount validation for other party types

* chore: Validation for return allocations

* chore: minor typo

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
(cherry picked from commit 9d27a25e5f)

* chore: remove unnecessary donor party type check

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
(cherry picked from commit 3d0add81fa)
2023-06-19 07:23:46 +00:00
mergify[bot]
3d0add81fa fix: Allocated amount validation for other party types (#35741)
* fix: Allocated amount validation for other party types (#35741)

* fix: Allocated amount validation for other party types

* chore: Validation for return allocations

* chore: minor typo

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
(cherry picked from commit 9d27a25e5f)

* chore: remove unnecessary donor party type check

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-19 12:53:00 +05:30
mergify[bot]
46d0b7d317 fix: loan interest accrual date (#35695)
fix: loan interest accrual date (#35695)

fix: loan interest accrual date

---------

Co-authored-by: Abhinav Raut <abhinav.raut@zerodha.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 2a24423ad2)

Co-authored-by: Abhinav Raut <abhinavrautcs@gmail.com>
2023-06-19 09:14:01 +05:30
mergify[bot]
0a8b7148a5 fix: add validation for QI in PR (backport #35677) (#35758)
fix: add validation for QI in PR

(cherry picked from commit 2c1ab569a7)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-17 13:08:06 +05:30
Frappe PR Bot
6400a574b6 chore(release): Bumped to Version 13.51.4
## [13.51.4](https://github.com/frappe/erpnext/compare/v13.51.3...v13.51.4) (2023-06-16)

### Bug Fixes

* Incorrect field while calculating Tax withholding net total ([a98a13b](a98a13b683))
2023-06-16 06:54:26 +00:00
Deepesh Garg
eaa1589331 Merge pull request #35734 from frappe/mergify/bp/version-13/pr-35733
fix: Incorrect field while calculating Tax withholding net total (backport #35733)
2023-06-16 12:22:48 +05:30
Deepesh Garg
d49a8ad74f chore: resolve conflicts 2023-06-16 12:22:23 +05:30
Deepesh Garg
a98a13b683 fix: Incorrect field while calculating Tax withholding net total
(cherry picked from commit 571c977e8e)

# Conflicts:
#	erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
2023-06-16 06:51:29 +00:00
Deepesh Garg
e37b6bbbf1 Merge pull request #35733 from deepeshgarg007/tax_withholding_limit_consumed
fix: Incorrect field while calculating Tax withholding net total
2023-06-16 12:20:44 +05:30
Deepesh Garg
bcc8a45c4e Merge branch 'version-13-hotfix' into tax_withholding_limit_consumed 2023-06-16 12:18:57 +05:30
Deepesh Garg
571c977e8e fix: Incorrect field while calculating Tax withholding net total 2023-06-16 12:17:31 +05:30
Frappe PR Bot
ac9f1fefe6 chore(release): Bumped to Version 13.51.3
## [13.51.3](https://github.com/frappe/erpnext/compare/v13.51.2...v13.51.3) (2023-06-16)

### Bug Fixes

* Incorrect field while calculating Tax withholding net total ([f8a8cf3](f8a8cf3046))
2023-06-16 05:51:43 +00:00
Deepesh Garg
513da54b6d Merge pull request #35731 from frappe/mergify/bp/version-13/pr-35730
fix: Incorrect field while calculating Tax withholding net total (#35730)
2023-06-16 11:20:00 +05:30
Deepesh Garg
f8a8cf3046 fix: Incorrect field while calculating Tax withholding net total
(cherry picked from commit b95d459812)
2023-06-16 05:49:03 +00:00
Deepesh Garg
1685305b53 Merge pull request #35730 from deepeshgarg007/tax_ithholding_v13
fix: Incorrect field while calculating Tax withholding net total
2023-06-16 11:18:22 +05:30
Deepesh Garg
b95d459812 fix: Incorrect field while calculating Tax withholding net total 2023-06-16 11:15:42 +05:30
mergify[bot]
0e11317303 fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (backport #35714) (#35716)
* fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (#35714)

fix: on asset scrap, don't add gl entry for acc. depr. if no acc. depr.
(cherry picked from commit bb39a2cac7)

# Conflicts:
#	erpnext/assets/doctype/asset/depreciation.py

* chore: fix conflict

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-15 17:40:42 +05:30
Frappe PR Bot
9a659254e3 chore(release): Bumped to Version 13.51.2
## [13.51.2](https://github.com/frappe/erpnext/compare/v13.51.1...v13.51.2) (2023-06-14)

### Bug Fixes

* **accounts:** validate payment entry references with latest data. (backport [#31166](https://github.com/frappe/erpnext/issues/31166)) ([#35674](https://github.com/frappe/erpnext/issues/35674)) ([4d4f218](4d4f218175))
* Asset Depreciation Ledger Report - Add Total Row Checkbox Enabled ([3831c79](3831c7920d))
* calculate wdv depr schedule properly for existing assets [v13] ([#35615](https://github.com/frappe/erpnext/issues/35615)) ([97f4af8](97f4af8d97))
* CSS not applied to product title (backport [#35582](https://github.com/frappe/erpnext/issues/35582)) ([#35635](https://github.com/frappe/erpnext/issues/35635)) ([1b69b37](1b69b37229))
* don't set default payment amount in case of invoice return (backport [#35645](https://github.com/frappe/erpnext/issues/35645)) ([#35648](https://github.com/frappe/erpnext/issues/35648)) ([8e3636f](8e3636ff53))
* Lower deduction certificate not getting applied ([#35667](https://github.com/frappe/erpnext/issues/35667)) ([c2bf8e3](c2bf8e3502))
* make showing taxes as table in print configurable ([#35672](https://github.com/frappe/erpnext/issues/35672)) ([4c2c037](4c2c037a86))
* Project in item-wise sales register ([#35596](https://github.com/frappe/erpnext/issues/35596)) ([9d5b500](9d5b500060))
* savepoint policy assignment submission, log errors & inform the user about failures ([#35507](https://github.com/frappe/erpnext/issues/35507)) ([4a35ff0](4a35ff0e57))

### Performance Improvements

* refactor `get_all_nodes` in Org Chart ([986a90e](986a90efe0))
2023-06-14 06:09:31 +00:00
Deepesh Garg
e75ca14a88 Merge pull request #35665 from frappe/version-13-hotfix
chore: release v13
2023-06-14 11:37:59 +05:30
mergify[bot]
c2bf8e3502 fix: Lower deduction certificate not getting applied (#35667)
* fix: Lower deduction certificate not getting applied (#35667)

(cherry picked from commit 937c0feefe)

# Conflicts:
#	erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py

* chore: Resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-14 09:01:54 +05:30
mergify[bot]
4d4f218175 fix(accounts): validate payment entry references with latest data. (backport #31166) (#35674)
* fix(accounts): validate payment entry references with latest data. (#31166)

* test: payment entry over allocation.

* fix: validate allocated_amount against latest outstanding amount.

* fix: payment entry get outstanding documents for advance payments

* fix: only fetch latest outstanding_amount.

* fix: throw if reference is allocated

* test: throw error if a reference has been partially allocated after inital creation.

* chore: test name

* fix: remove unused part of test

* chore: linter

* chore: more user friendly error messages

* fix: only validate outstanding amount if partly paid and don't filter by cost center

* chore: minor refactor for doc.cost_center

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 20de27d480)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/test_payment_entry.py

* chore: resolve conflicts

* chore: resolve more conflicts

* chore: don't validate allocated amount in case of donation

---------

Co-authored-by: Devin Slauenwhite <devin.slauenwhite@gmail.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-13 23:01:51 +05:30
mergify[bot]
4c2c037a86 fix: make showing taxes as table in print configurable (#35672)
* fix: make showing taxes as table in print configurable (#35672)

(cherry picked from commit 491a50a027)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json

* chore: fixing conflicts

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-13 21:38:16 +05:30
mergify[bot]
4f79214ae6 Stock aging report fix when called in dashboard chart (backport #35671) (#35676)
fix: get_range_age conditions fixed (#35671)

see https://github.com/frappe/erpnext/issues/35669

(cherry picked from commit 9f669d4c2f)

Co-authored-by: Hossein Yousefian <86075967+ihosseinu@users.noreply.github.com>
2023-06-13 19:24:21 +05:30
Rucha Mahabal
5b37abd2d6 Merge pull request #35663 from ruchamahabal/perf-org-charts-v13 2023-06-13 13:20:09 +05:30
Rucha Mahabal
a24d488817 test: get children for org chart 2023-06-13 12:29:53 +05:30
Rucha Mahabal
986a90efe0 perf: refactor get_all_nodes in Org Chart 2023-06-13 12:07:15 +05:30
Rucha Mahabal
4a35ff0e57 fix: savepoint policy assignment submission, log errors & inform the user about failures (#35507) 2023-06-13 12:03:55 +05:30
mergify[bot]
8e3636ff53 fix: don't set default payment amount in case of invoice return (backport #35645) (#35648)
* fix: don't set default payment amount in case of invoice return (#35645)

(cherry picked from commit 79483cc90e)

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

* chore: fixing conflict

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-12 18:44:56 +05:30
Trusted Computer
1b69b37229 fix: CSS not applied to product title (backport #35582) (#35635)
In an erpnext website, the /all-products route shows website items that have been published to the web site.

In the list view (erpnext/e_commerce/product_ui/list.js), the css class is null for the product title. Instead, inline style statements have been added in that can not be modified by overriding CSS.

This fix uses a similar approach to that which is taken in the grid view (erpnext/e_commerce/product_ui/grid.js). It removes the null CSS parameter in the product title link as well as the inline style statement. Then, as in the grid view, the product title is wrapped in a div tag with the product_title CSS class.

This makes it possible to style the product title as desired with a CSS override.
2023-06-12 14:39:41 +05:30
Anand Baburajan
97f4af8d97 fix: calculate wdv depr schedule properly for existing assets [v13] (#35615)
* fix: calculate wdv depr schedule properly for existing assets

* fix: calculate wdv depr schedule properly for existing assets properly
2023-06-08 23:16:35 +05:30
mergify[bot]
9d5b500060 fix: Project in item-wise sales register (#35596)
fix: Project in item-wise sales register (#35596)

(cherry picked from commit f732cac678)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-07 22:36:49 +05:30
AJAY NATARAJAN
3831c7920d fix: Asset Depreciation Ledger Report - Add Total Row Checkbox Enabled 2023-06-07 21:48:27 +05:30
Frappe PR Bot
f182fc1f8e chore(release): Bumped to Version 13.51.1
## [13.51.1](https://github.com/frappe/erpnext/compare/v13.51.0...v13.51.1) (2023-06-07)

### Bug Fixes

* Interest Accrual on Loan Topup ([#35555](https://github.com/frappe/erpnext/issues/35555)) ([1415f40](1415f40dfb))
* Task gantt popup style ([5544801](55448017d7))
2023-06-07 06:18:08 +00:00
Deepesh Garg
1897d6f214 Merge pull request #35570 from frappe/version-13-hotfix
chore: release v13
2023-06-07 11:46:43 +05:30
mergify[bot]
1415f40dfb fix: Interest Accrual on Loan Topup (#35555)
fix: Interest Accrual on Loan Topup (#35555)

* fix: Interest Accrual on Loan Topup

* chore: CI

* chore: Ignore test

(cherry picked from commit 2ffcca6f10)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-07 10:11:54 +05:30
Suraj Shetty
09cf050b0d Merge pull request #35532 from frappe/mergify/bp/version-13-hotfix/pr-35530
fix: Task gantt popup style and info (backport #35530)
2023-06-02 11:11:35 +05:30
Suraj Shetty
55448017d7 fix: Task gantt popup style
(cherry picked from commit f7b2d103e7)
2023-06-02 05:39:53 +00:00
ruthra kumar
202513ae6a Merge pull request #35520 from frappe/mergify/bp/version-13-hotfix/pr-35112
refactor(Gross Profit): simplify group by invoice logic (backport #35112)
2023-06-01 15:13:02 +05:30
ruthra kumar
e3a8a8d195 refactor: simplify group by invoice logic
(cherry picked from commit 092c4b4c58)
2023-06-01 09:05:35 +00:00
Sagar Vora
169af8f9f8 Merge pull request #35502 from frappe/mergify/bp/version-13-hotfix/pr-35500 2023-05-31 14:46:24 +05:30
Sagar Vora
f0580b0e4d chore: remove whitelisting for method not accessed from UI
(cherry picked from commit 517d8a03ec)
2023-05-31 09:15:46 +00:00
Frappe PR Bot
b5b34c14b2 chore(release): Bumped to Version 13.51.0
# [13.51.0](https://github.com/frappe/erpnext/compare/v13.50.6...v13.51.0) (2023-05-31)

### Bug Fixes

* force to do reposting for cancelled document ([0228933](022893391b))
* **Gross Profit:** 'company' column is ambiguous in filter ([270eb1d](270eb1db4d))
* incorrect `POS Reserved Qty` in `Stock Projected Qty` Report ([#35437](https://github.com/frappe/erpnext/issues/35437)) ([139a193](139a193f1d))
* incorrect depr schedule and posting dates on selling of existing assets [v13] ([#35404](https://github.com/frappe/erpnext/issues/35404)) ([20d3381](20d3381010))
* monthly WDV depr schedule for existing assets [v13] ([#35461](https://github.com/frappe/erpnext/issues/35461)) ([6f43829](6f43829c32))
* retention stock entry: grab conversion factor from source ([d8dd22a](d8dd22adaf))

### Features

* Allow ceil & floor functions in salary slip formulae ([#35475](https://github.com/frappe/erpnext/issues/35475)) ([63fba9d](63fba9db39))
2023-05-31 05:49:42 +00:00
Deepesh Garg
839a1f0454 Merge pull request #35474 from frappe/version-13-hotfix
chore: release v13
2023-05-31 11:14:32 +05:30
Rucha Mahabal
63fba9db39 feat: Allow ceil & floor functions in salary slip formulae (#35475) 2023-05-30 16:11:44 +05:30
Sagar Sharma
00fd08c7bc Merge pull request #35468 from frappe/mergify/bp/version-13-hotfix/pr-35459
fix: retention stock entry: grab conversion factor from source (backport #35459)
2023-05-30 11:30:41 +05:30
Marc de Lima Lucio
d8dd22adaf fix: retention stock entry: grab conversion factor from source
(cherry picked from commit 6954f538c9)
2023-05-30 05:36:33 +00:00
Anand Baburajan
6f43829c32 fix: monthly WDV depr schedule for existing assets [v13] (#35461)
fix: monthly wdv depr schedule for existing assets
2023-05-29 23:18:04 +05:30
Sagar Sharma
3e95d56240 Merge pull request #35456 from frappe/mergify/bp/version-13-hotfix/pr-35328
fix: force to do reposting for cancelled document (backport #35328)
2023-05-29 15:56:54 +05:30
s-aga-r
44cb62824d chore: conflicts 2023-05-29 15:22:22 +05:30
Rohit Waghchaure
022893391b fix: force to do reposting for cancelled document
(cherry picked from commit 6e661e7c0e)

# Conflicts:
#	erpnext/controllers/stock_controller.py
2023-05-29 09:38:35 +00:00
mergify[bot]
139a193f1d fix: incorrect POS Reserved Qty in Stock Projected Qty Report (#35437)
* fix: incorrect `POS Reserved Qty` in `Stock Projected Qty` Report

(cherry picked from commit 027de41600)

# Conflicts:
#	erpnext/accounts/doctype/pos_invoice/pos_invoice.py

* chore: `conflicts`

---------

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2023-05-29 09:24:20 +05:30
ruthra kumar
4f5ee6876d Merge pull request #35420 from frappe/mergify/bp/version-13-hotfix/pr-35417
fix(Gross Profit): 'company' column is ambiguous in filter (backport #35417)
2023-05-25 16:44:56 +05:30
ruthra kumar
270eb1db4d fix(Gross Profit): 'company' column is ambiguous in filter
(cherry picked from commit 448712f719)
2023-05-25 09:37:39 +00:00
Anand Baburajan
20d3381010 fix: incorrect depr schedule and posting dates on selling of existing assets [v13] (#35404)
fix: calc depr amount properly on selling of existing assets and fix incorrect posting dates
2023-05-24 14:24:18 +05:30
Frappe PR Bot
fd04bd0f72 chore(release): Bumped to Version 13.50.6
## [13.50.6](https://github.com/frappe/erpnext/compare/v13.50.5...v13.50.6) (2023-05-24)

### Bug Fixes

* allow over-payment against SO ([#35079](https://github.com/frappe/erpnext/issues/35079)) ([eb243c2](eb243c2470))
* bypass flag in Customer Group wasn't effective ([f0c9d89](f0c9d89aab))
* change field-type to remove currency field from total row in export ([f65be40](f65be40037))
* consider 0 if rate/qty are null (backport [#35338](https://github.com/frappe/erpnext/issues/35338)) ([#35341](https://github.com/frappe/erpnext/issues/35341)) ([387f8b9](387f8b9e1a))
* depreciation schedule for existing assets [v14] (backport [#35255](https://github.com/frappe/erpnext/issues/35255)) ([#35347](https://github.com/frappe/erpnext/issues/35347)) ([7506132](7506132861))
* error while saving job card ([d6427cf](d6427cfe53))
* get_query filters ([2aa7729](2aa7729243))
* Incorrect Earned Leaves Proration ([#35156](https://github.com/frappe/erpnext/issues/35156)) ([dc04b24](dc04b24234))
* linter ([0a42e6f](0a42e6ff0f))
* non manufacturing items/fixed asset items in BOM ([66ba74f](66ba74f3fc))
* Pick List TypeError ([137898d](137898d55d))
* tds incorrectly calculated for invoice that are below threshold ([6c170ab](6c170abdf9))
* **test:** cumulative threshold checks ([06deecb](06deecbd92))
* use flt instead of mandatory field ([f63b866](f63b866de3))
2023-05-24 03:03:13 +00:00
Deepesh Garg
166ec0e58c Merge pull request #35393 from frappe/version-13-hotfix
chore: release v13
2023-05-24 08:31:15 +05:30
Saurabh
1e1dddfe6c Merge pull request #35387 from saurabh6790/minor-fix
fix: change field-type to remove currency field from total row in export
2023-05-23 18:53:45 +05:30
Saurabh
0a42e6ff0f fix: linter 2023-05-23 15:36:23 +05:30
Sagar Sharma
97f9c0d53f Merge pull request #35391 from frappe/mergify/bp/version-13-hotfix/pr-35303
fix: Pick List TypeError (backport #35303)
2023-05-23 15:05:41 +05:30
Sagar Sharma
137898d55d fix: Pick List TypeError
(cherry picked from commit a111917114)
2023-05-23 08:51:32 +00:00
Saurabh
f65be40037 fix: change field-type to remove currency field from total row in export 2023-05-23 12:39:20 +05:30
Sagar Sharma
75f4a616f1 Merge pull request #35384 from frappe/mergify/bp/version-13-hotfix/pr-35380
fix: TypeError while saving Job card (backport #35380)
2023-05-23 10:59:15 +05:30
Sagar Sharma
8d97f8b0b7 chore: conflicts 2023-05-23 10:54:27 +05:30
vishnu
f63b866de3 fix: use flt instead of mandatory field
(cherry picked from commit 8c34cc0e00)

# Conflicts:
#	erpnext/manufacturing/doctype/job_card/job_card.json
2023-05-23 05:20:21 +00:00
vishnu
d6427cfe53 fix: error while saving job card
(cherry picked from commit a209fb4b64)

# Conflicts:
#	erpnext/manufacturing/doctype/job_card/job_card.json
2023-05-23 05:20:21 +00:00
rohitwaghchaure
774092343a Merge pull request #35374 from frappe/mergify/bp/version-13-hotfix/pr-35227
fix: non manufacturing items/fixed asset items in BOM (backport #35227)
2023-05-22 13:48:10 +05:30
rohitwaghchaure
2aa7729243 fix: get_query filters 2023-05-22 13:16:06 +05:30
Rohit Waghchaure
66ba74f3fc fix: non manufacturing items/fixed asset items in BOM
(cherry picked from commit aba8431d70)
2023-05-22 07:43:34 +00:00
JunKangChin
dc04b24234 fix: Incorrect Earned Leaves Proration (#35156) 2023-05-21 21:36:10 +05:30
mergify[bot]
eb243c2470 fix: allow over-payment against SO (#35079)
fix: allow over-payment against SO (#35079)

(cherry picked from commit 870b02b03c)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-05-20 21:03:59 +05:30
ruthra kumar
f7ed4ecd56 Merge pull request #35299 from ruthra-kumar/replace_join_with_subquery
refactor: replace join with subquery in PCV
2023-05-19 11:00:42 +05:30
Frappe PR Bot
6bc8749eaf chore(release): Bumped to Version 13.50.5
## [13.50.5](https://github.com/frappe/erpnext/compare/v13.50.4...v13.50.5) (2023-05-18)

### Bug Fixes

* tds incorrectly calculated for invoice that are below threshold ([2e3f8e8](2e3f8e8846))
* **test:** cumulative threshold checks ([d316955](d316955d18))
2023-05-18 14:55:22 +00:00
ruthra kumar
2747df78ac Merge pull request #35360 from frappe/mergify/bp/version-13/pr-35335
fix: tds incorrectly calculated for invoice that are below threshold (backport #35335)
2023-05-18 20:23:24 +05:30
ruthra kumar
d316955d18 fix(test): cumulative threshold checks
(cherry picked from commit 132846bbd1)
2023-05-18 14:29:45 +00:00
ruthra kumar
2e3f8e8846 fix: tds incorrectly calculated for invoice that are below threshold
Two purchase invoices for the same supplier, using different tax
withholding categories have this issue.

| Category | single | cumulative |
|----------+--------+------------|
| cat1     |    100 |        500 |
| cat2     |   1000 |       5000 |

1. PINV1 of net total: 105/- uses cat1. TDS is calculated as it
breached single threshold
2. PINV2 of net total: 200/- uses cat2. TDS incorrectly calculated as
PINV1 already has TDS calculated and 'consider_party_ledger_amount' is enabled.

(cherry picked from commit 84b7c1bba0)
2023-05-18 14:29:45 +00:00
ruthra kumar
9af4e117d4 Merge pull request #35354 from frappe/mergify/bp/version-13-hotfix/pr-35335
fix: tds incorrectly calculated for invoice that are below threshold (backport #35335)
2023-05-18 14:36:28 +05:30
ruthra kumar
6191cfee4c Merge pull request #35356 from frappe/mergify/bp/version-13-hotfix/pr-35142
fix: ineffective bypass flag for Credit Limit in Customer Group (backport #35142)
2023-05-18 13:31:14 +05:30
ruthra kumar
f0c9d89aab fix: bypass flag in Customer Group wasn't effective
(cherry picked from commit f9a4972cb6)
2023-05-18 07:32:36 +00:00
ruthra kumar
06deecbd92 fix(test): cumulative threshold checks
(cherry picked from commit 132846bbd1)
2023-05-18 07:09:19 +00:00
ruthra kumar
6c170abdf9 fix: tds incorrectly calculated for invoice that are below threshold
Two purchase invoices for the same supplier, using different tax
withholding categories have this issue.

| Category | single | cumulative |
|----------+--------+------------|
| cat1     |    100 |        500 |
| cat2     |   1000 |       5000 |

1. PINV1 of net total: 105/- uses cat1. TDS is calculated as it
breached single threshold
2. PINV2 of net total: 200/- uses cat2. TDS incorrectly calculated as
PINV1 already has TDS calculated and 'consider_party_ledger_amount' is enabled.

(cherry picked from commit 84b7c1bba0)
2023-05-18 07:09:19 +00:00
mergify[bot]
7506132861 fix: depreciation schedule for existing assets [v14] (backport #35255) (#35347)
* fix: depreciation schedule for existing assets [v14] (#35255)

* fix: depreciation schedule for existing assets

* chore: correct logic for existing assets and fix test

(cherry picked from commit 0a080efce2)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.py

* chore: fix conflict

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-05-17 22:52:13 +05:30
mergify[bot]
387f8b9e1a fix: consider 0 if rate/qty are null (backport #35338) (#35341)
fix: consider 0 if rate/qty are null (#35338)

[skip ci]

(cherry picked from commit e5c86bc2e8)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-05-17 16:17:25 +05:30
Frappe PR Bot
c2ae8eaec0 chore(release): Bumped to Version 13.50.4
## [13.50.4](https://github.com/frappe/erpnext/compare/v13.50.3...v13.50.4) (2023-05-16)

### Bug Fixes

* add missing options for `Content Align` ([e37b903](e37b9030fb))
* cancelled vouchers in tax withheld vouchers list ([#35309](https://github.com/frappe/erpnext/issues/35309)) ([188cfc2](188cfc2e3c))
* internal transfer condition ([a1d7170](a1d717053a))
* **Salary Slip:** exchange rate overwritten on form load ([#507](https://github.com/frappe/erpnext/issues/507)) ([#35245](https://github.com/frappe/erpnext/issues/35245)) ([8b3d6ee](8b3d6ee7b0))
* update reference data for statistical component ([77f548c](77f548c814))
2023-05-16 16:59:38 +00:00
Deepesh Garg
f5f88bb62c Merge pull request #35323 from frappe/version-13-hotfix
chore: release v13
2023-05-16 22:28:02 +05:30
mergify[bot]
188cfc2e3c fix: cancelled vouchers in tax withheld vouchers list (#35309)
fix: cancelled vouchers in tax withheld vouchers list (#35309)

(cherry picked from commit 776a83066d)

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2023-05-16 18:58:23 +05:30
Saurabh
c7c2bad6ab Merge pull request #35267 from saurabh6790/fix-statistical-component-calculation
fix: update reference data for statistical component
2023-05-15 11:09:50 +05:30
ruthra kumar
e6a9252f79 refactor: replace join with sub-query for fetching accounts 2023-05-14 16:54:56 +05:30
Sagar Sharma
4a9ad09c7f Merge pull request #35284 from frappe/mergify/bp/version-13-hotfix/pr-35275
fix: add missing options for `Content Align` (backport #35275)
2023-05-13 09:41:55 +05:30
Sagar Sharma
e37b9030fb fix: add missing options for Content Align
(cherry picked from commit d16caa2d2c)
2023-05-13 04:05:15 +00:00
Saurabh
77f548c814 fix: update reference data for statistical component 2023-05-12 12:10:40 +05:30
Frappe PR Bot
7626d51db1 chore(release): Bumped to Version 13.50.3
## [13.50.3](https://github.com/frappe/erpnext/compare/v13.50.2...v13.50.3) (2023-05-11)

### Bug Fixes

* internal transfer condition ([ac26e4b](ac26e4ba2a))
2023-05-11 17:26:55 +00:00
rohitwaghchaure
48e5846ed5 Merge pull request #35260 from frappe/mergify/bp/version-13/pr-35259
fix: internal transfer condition (backport #35158) (backport #35259)
2023-05-11 22:54:34 +05:30
Rohit Waghchaure
ac26e4ba2a fix: internal transfer condition
(cherry picked from commit b5a2ccf21d)
(cherry picked from commit a1d717053a)
2023-05-11 15:08:08 +00:00
rohitwaghchaure
8b9f8c6ab7 Merge pull request #35259 from frappe/mergify/bp/version-13-hotfix/pr-35158
fix: internal transfer condition (backport #35158)
2023-05-11 20:36:41 +05:30
Rohit Waghchaure
a1d717053a fix: internal transfer condition
(cherry picked from commit b5a2ccf21d)
2023-05-11 14:38:38 +00:00
Rucha Mahabal
8b3d6ee7b0 fix(Salary Slip): exchange rate overwritten on form load (#507) (#35245) 2023-05-10 16:16:50 +05:30
Frappe PR Bot
1380f7a7ec chore(release): Bumped to Version 13.50.2
## [13.50.2](https://github.com/frappe/erpnext/compare/v13.50.1...v13.50.2) (2023-05-10)

### Bug Fixes

* handle empty FBs properly in TB and GL [v14] (backport [#35189](https://github.com/frappe/erpnext/issues/35189)) ([#35192](https://github.com/frappe/erpnext/issues/35192)) ([e2af66c](e2af66c7be))
2023-05-10 05:40:17 +00:00
Deepesh Garg
2825253339 Merge pull request #35223 from frappe/version-13-hotfix
chore: release v13
2023-05-10 11:08:54 +05:30
Frappe PR Bot
40cfd5215c chore(release): Bumped to Version 13.50.1
## [13.50.1](https://github.com/frappe/erpnext/compare/v13.50.0...v13.50.1) (2023-05-06)

### Bug Fixes

* handle empty FBs properly in TB and GL [v14] (backport [#35189](https://github.com/frappe/erpnext/issues/35189)) (backport [#35192](https://github.com/frappe/erpnext/issues/35192)) ([#35195](https://github.com/frappe/erpnext/issues/35195)) ([af8142c](af8142cf85))
2023-05-06 18:13:48 +00:00
mergify[bot]
af8142cf85 fix: handle empty FBs properly in TB and GL [v14] (backport #35189) (backport #35192) (#35195)
fix: handle empty FBs properly in TB and GL [v14] (backport #35189) (#35192)

fix: handle empty FBs properly in TB and GL [v14] (#35189)

fix: handle empty FBs properly in TB and GL
(cherry picked from commit ed5f39c2c2)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
(cherry picked from commit e2af66c7be)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-06 23:41:57 +05:30
mergify[bot]
e2af66c7be fix: handle empty FBs properly in TB and GL [v14] (backport #35189) (#35192)
fix: handle empty FBs properly in TB and GL [v14] (#35189)

fix: handle empty FBs properly in TB and GL
(cherry picked from commit ed5f39c2c2)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-05-06 17:44:49 +05:30
Frappe PR Bot
ef2d4febdd chore(release): Bumped to Version 13.50.0
# [13.50.0](https://github.com/frappe/erpnext/compare/v13.49.14...v13.50.0) (2023-05-03)

### Bug Fixes

* allow user to set standard deductions in income tax slab without allowing other exemptions ([c5261cd](c5261cde9c))
* check for session user rather than owner ([7d6e2f9](7d6e2f979f))
* conflicts ([778ba69](778ba6956c))
* conflicts ([b19b0a4](b19b0a4a98))
* conflicts ([6bdf143](6bdf143084))
* don't allow to make reposting for the closed period ([b31d8ee](b31d8eec05))
* handle expected_value_after_useful_life properly in asset value adjustment (backport [#35117](https://github.com/frappe/erpnext/issues/35117)) ([#35120](https://github.com/frappe/erpnext/issues/35120)) ([635559d](635559d905))
* handle finance book properly in trial balance and general ledger ([#35136](https://github.com/frappe/erpnext/issues/35136)) ([9a37603](9a376039aa))
* Hyperlink in Quality Inspection Summary ([54388e8](54388e8d92))
* Naming series error in Journal Entry template ([#35084](https://github.com/frappe/erpnext/issues/35084)) ([d3c769c](d3c769c183))
* per_billed condition for Payment Entry ([#34969](https://github.com/frappe/erpnext/issues/34969)) ([563e5c0](563e5c0b69))
* test case ([db6d0e0](db6d0e03f5))

### Features

* validate repost item valuation against accounts freeze date ([a852dc1](a852dc1f11))
2023-05-03 06:38:15 +00:00
Deepesh Garg
cb0d567d7b Merge pull request #35130 from frappe/version-13-hotfix
chore: release v13
2023-05-03 12:06:38 +05:30
mergify[bot]
9a376039aa fix: handle finance book properly in trial balance and general ledger (#35136)
fix: handle finance book properly in trial balance and general ledger [v14] (#35136)

fix: handle FBs properly in general ledger and trial balance
(cherry picked from commit 344c339484)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-05-03 08:25:01 +05:30
Saurabh
2f74026513 Merge pull request #35114 from saurabh6790/consider-standard-exemption
fix: allow user to set standard deductions in income tax slab without allowing other exemptions
2023-05-02 11:38:30 +05:30
rohitwaghchaure
740313ff09 Merge pull request #35122 from frappe/mergify/bp/version-13-hotfix/pr-35118
fix: don't allow to make reposting for the closed period (backport #35118)
2023-05-02 00:00:58 +05:30
rohitwaghchaure
db6d0e03f5 fix: test case 2023-05-01 23:34:31 +05:30
rohitwaghchaure
778ba6956c fix: conflicts 2023-05-01 20:55:52 +05:30
rohitwaghchaure
b19b0a4a98 fix: conflicts 2023-05-01 20:54:32 +05:30
Rohit Waghchaure
b31d8eec05 fix: don't allow to make reposting for the closed period
(cherry picked from commit f751727149)

# Conflicts:
#	erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
#	erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
2023-05-01 15:12:01 +00:00
rohitwaghchaure
078161cf6b Merge pull request #35116 from frappe/mergify/bp/version-13-hotfix/pr-33013
fix: validate repost item valuation against accounts freeze date (backport #33013)
2023-05-01 20:41:00 +05:30
mergify[bot]
635559d905 fix: handle expected_value_after_useful_life properly in asset value adjustment (backport #35117) (#35120)
fix: handle expected_value_after_useful_life properly in asset value adjustment (#35117)

(cherry picked from commit 80230fec3e)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-05-01 20:00:04 +05:30
rohitwaghchaure
6bdf143084 fix: conflicts 2023-05-01 18:26:51 +05:30
Dany Robert
198a64d574 chore: pre-commit
(cherry picked from commit 88a0aa4077)
2023-05-01 11:50:17 +00:00
Dany Robert
7d6e2f979f fix: check for session user rather than owner
(cherry picked from commit b482e3876d)
2023-05-01 11:50:17 +00:00
Dany Robert
6992e727cf chore: pre-commit
(cherry picked from commit be15419bd5)

# Conflicts:
#	erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
2023-05-01 11:50:16 +00:00
Dany Robert
a852dc1f11 feat: validate repost item valuation against accounts freeze date
(cherry picked from commit 61f05132db)

# Conflicts:
#	erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
2023-05-01 11:50:16 +00:00
Saurabh
c5261cde9c fix: allow user to set standard deductions in income tax slab without allowing other exemptions 2023-05-01 13:33:26 +05:30
mergify[bot]
d3c769c183 fix: Naming series error in Journal Entry template (#35084)
fix: Naming series error in Journal Entry template (#35084)

(cherry picked from commit f3b3dabb9a)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-05-01 13:02:28 +05:30
mergify[bot]
563e5c0b69 fix: per_billed condition for Payment Entry (#34969)
fix: per_billed condition for Payment Entry (#34969)

fix: per_billed condition for Payment Entry (#34969)

(cherry picked from commit d6bc8bba8b)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
(cherry picked from commit f9f42c7e98)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-05-01 10:51:23 +05:30
Sagar Sharma
5746ddce84 Merge pull request #35089 from frappe/mergify/bp/version-13-hotfix/pr-35081
fix: Hyperlink in Quality Inspection Summary (backport #35081)
2023-04-28 12:53:51 +05:30
Nihantra Patel
54388e8d92 fix: Hyperlink in Quality Inspection Summary
(cherry picked from commit 72dd7884a8)
2023-04-28 07:19:14 +00:00
Deepesh Garg
784ea7cf48 Merge pull request #35073 from frappe/mergify/bp/version-13/pr-35032
chore: set correct currency symbol in salary register for multi-currency salary slip (backport #35032)
2023-04-27 13:29:08 +05:30
Saurabh
fc42e026ab chore: fix linter
(cherry picked from commit 31bda37970)
2023-04-27 06:10:17 +00:00
Saurabh
cef7126a35 chore: set correct currency symbol in salary register for multi-currency salary slip
(cherry picked from commit 83afaf48df)
2023-04-27 06:10:17 +00:00
Saurabh
297facc1cb Merge pull request #35032 from saurabh6790/salary-register-report-fix
chore: set correct currency symbol in salary register for multi-currency salary slip
2023-04-26 16:36:15 +05:30
Saurabh
31bda37970 chore: fix linter 2023-04-26 15:44:34 +05:30
Frappe PR Bot
1d6917f340 chore(release): Bumped to Version 13.49.14
## [13.49.14](https://github.com/frappe/erpnext/compare/v13.49.13...v13.49.14) (2023-04-25)

### Bug Fixes

* `PermissionError` in Work Order ([5680045](5680045f2b))
* Advance payment against payment terms ([#34872](https://github.com/frappe/erpnext/issues/34872)) ([d215a85](d215a85747))
* internal Purchase Receipt GL Entries ([b73422e](b73422e4ee))
* Payment entry with TDS in bank reco statement ([#34961](https://github.com/frappe/erpnext/issues/34961)) ([5f28b1d](5f28b1d330))
* Payment Request flow fixes from Order to Payment Entry ([#33350](https://github.com/frappe/erpnext/issues/33350)) ([d5a80b5](d5a80b5615))
* SLA permissions ([#34981](https://github.com/frappe/erpnext/issues/34981)) ([c1187be](c1187bed26))
* **test:** `test_backdated_stock_reco_cancellation_future_negative_stock` ([d010b04](d010b048dc))
* Unable to allocate advance against invoice ([#35007](https://github.com/frappe/erpnext/issues/35007)) ([61a3121](61a3121172))
* use filter_by_finance_book instead of only_depreciable_assets in fixed asset register (backport [#35031](https://github.com/frappe/erpnext/issues/35031)) ([#35036](https://github.com/frappe/erpnext/issues/35036)) ([be2095a](be2095ad03))
* validation for internal transfer entry ([91b5a33](91b5a33564))
* value of depreciable assets not updating after manual depr entry [v14] (backport [#35010](https://github.com/frappe/erpnext/issues/35010)) ([#35029](https://github.com/frappe/erpnext/issues/35029)) ([9087ac0](9087ac0829))
* wrong qty of remaining work orders to be created when using "Create" > "Work Order" ([#34726](https://github.com/frappe/erpnext/issues/34726)) ([189b020](189b020d22))
2023-04-25 16:57:14 +00:00
Deepesh Garg
b85d8946f7 Merge pull request #35033 from frappe/version-13-hotfix
chore: release v13
2023-04-25 22:24:49 +05:30
mergify[bot]
5f28b1d330 fix: Payment entry with TDS in bank reco statement (#34961)
fix: Payment entry with TDS in bank reco statement (#34961)

(cherry picked from commit ecea9b44a3)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-25 21:13:42 +05:30
mergify[bot]
61a3121172 fix: Unable to allocate advance against invoice (#35007)
fix: Unable to allocate advance against invoice (#35007)

(cherry picked from commit f7b50f2ade)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-25 20:51:35 +05:30
mergify[bot]
d5a80b5615 fix: Payment Request flow fixes from Order to Payment Entry (#33350)
* fix: Payment Request flow fixes from Order to Payment Entry

(cherry picked from commit dc178984ae)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.js
#	erpnext/public/js/controllers/transaction.js

* chore: Update test case

(cherry picked from commit e25b98b620)

* chore: More fixes

(cherry picked from commit 31c95deb88)

* chore: resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-25 20:49:18 +05:30
danjeremynavarro
189b020d22 fix: wrong qty of remaining work orders to be created when using "Create" > "Work Order" (#34726)
* fix: convert asynchronous field update to synchronous

* fix: wrong qty of remaining work orders to be created when using "Create" > "Work Order"
2023-04-25 18:53:52 +05:30
mergify[bot]
be2095ad03 fix: use filter_by_finance_book instead of only_depreciable_assets in fixed asset register (backport #35031) (#35036)
fix: use filter_by_finance_book instead of only_depreciable_assets in fixed asset register (#35031)

fix: use filter_by_finance_book instead of only_depreciable_assets
(cherry picked from commit e08d636bf7)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-04-25 15:53:06 +05:30
Saurabh
83afaf48df chore: set correct currency symbol in salary register for multi-currency salary slip 2023-04-25 14:58:37 +05:30
mergify[bot]
9087ac0829 fix: value of depreciable assets not updating after manual depr entry [v14] (backport #35010) (#35029)
* fix: value of depreciable assets not updating after manual depr entry [v14] (#35010)

* fix: update value of asset with calc_depr on after manual depr entry

* fix: value of asset with calc_depr on after manual depr entry not reflecting in asset_depr_and_bal report

* chore: add validation for depr journal entry

* test: manual_depr_for_depreciable_asset and manual_depr_w_incorrect_jv_voucher_type

* chore: unlink depreciable asset from manual depr entry

(cherry picked from commit 3c75e55cb9)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/journal_entry.js

* chore: fixed conflicts

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-04-25 13:33:51 +05:30
Sagar Sharma
7759d1e390 Merge pull request #34990 from frappe/mergify/bp/version-13-hotfix/pr-34981
fix: SLA permissions (backport #34981)
2023-04-22 10:31:41 +05:30
s-aga-r
cfa1a2b050 chore: conflicts 2023-04-22 10:08:26 +05:30
Ankush Menat
c1187bed26 fix: SLA permissions (#34981)
(cherry picked from commit ac871797b2)

# Conflicts:
#	erpnext/support/doctype/service_level_agreement/service_level_agreement.json
2023-04-21 15:44:45 +00:00
rohitwaghchaure
9957981039 Merge pull request #34984 from frappe/mergify/bp/version-13-hotfix/pr-34980
fix: validation for internal transfer entry (backport #34980)
2023-04-21 18:53:28 +05:30
rohitwaghchaure
82b46f2bfe chore: fix conflicts 2023-04-21 17:52:11 +05:30
Rohit Waghchaure
91b5a33564 fix: validation for internal transfer entry
(cherry picked from commit 19911b48fd)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.js
2023-04-21 12:16:43 +00:00
mergify[bot]
d215a85747 fix: Advance payment against payment terms (#34872)
* fix: Advance payment against payment terms (#34872)

(cherry picked from commit 5c75894065)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.py

* chore: resolve conflicts

* chore: fix tests

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-21 13:24:07 +05:30
Sagar Sharma
813b4d4de2 Merge pull request #34945 from frappe/mergify/bp/version-13-hotfix/pr-34912
fix: internal Purchase Receipt GL Entries (backport #34912)
2023-04-20 17:13:06 +05:30
Sagar Sharma
983140acd8 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-34912 2023-04-20 16:21:11 +05:30
s-aga-r
102ac9f74d chore: conflicts 2023-04-20 16:20:25 +05:30
Sagar Sharma
ebf8deb933 Merge pull request #34957 from frappe/mergify/bp/version-13-hotfix/pr-34953
fix: `PermissionError` in Work Order (backport #34953)
2023-04-20 16:01:36 +05:30
s-aga-r
5680045f2b fix: PermissionError in Work Order
(cherry picked from commit 8108b2de0a)
2023-04-20 10:28:04 +00:00
s-aga-r
d010b048dc fix(test): test_backdated_stock_reco_cancellation_future_negative_stock
(cherry picked from commit 11c8503180)
2023-04-20 06:06:05 +00:00
s-aga-r
046bf64fa3 test: add test case for internal PR GL Entries
(cherry picked from commit c86c543fbf)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
2023-04-20 06:06:05 +00:00
s-aga-r
b73422e4ee fix: internal Purchase Receipt GL Entries
(cherry picked from commit 6fca9adcd4)
2023-04-20 06:06:04 +00:00
Frappe PR Bot
acecd07fa2 chore(release): Bumped to Version 13.49.13
## [13.49.13](https://github.com/frappe/erpnext/compare/v13.49.12...v13.49.13) (2023-04-19)

### Bug Fixes

* change discuss forum url ([#34891](https://github.com/frappe/erpnext/issues/34891)) ([ba984ac](ba984acef2))
* unable to change `company` for manual `Serial No` entry ([5d51103](5d511035ec))
2023-04-19 01:36:18 +00:00
Deepesh Garg
5c6134f1b0 Merge pull request #34906 from frappe/version-13-hotfix
chore: release v13
2023-04-19 07:04:53 +05:30
Sagar Sharma
a2d2beb610 Merge pull request #34869 from frappe/mergify/bp/version-13-hotfix/pr-34858
fix: unable to change `company` for manual `Serial No` entry (backport #34858)
2023-04-18 21:29:02 +05:30
Sagar Sharma
3f4c322bef Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-34858 2023-04-18 11:38:11 +05:30
mergify[bot]
ba984acef2 fix: change discuss forum url (#34891)
fix: change discuss forum url (#34891)

[skip ci]

(cherry picked from commit dd93ea067e)

Co-authored-by: MohsinAli <mmatiyailol@gmail.com>
2023-04-18 07:42:18 +05:30
s-aga-r
98ed6445a8 chore: conflicts 2023-04-16 20:10:18 +05:30
s-aga-r
5d511035ec fix: unable to change company for manual Serial No entry
(cherry picked from commit fb3271c624)

# Conflicts:
#	erpnext/stock/doctype/serial_no/serial_no.json
2023-04-15 06:41:00 +00:00
mergify[bot]
7655a4f0d1 chore: update CODEOWNERS (#34817)
* chore: update CODEOWNERS

[skip ci]

(cherry picked from commit aa8b241d5a)

# Conflicts:
#	CODEOWNERS

* fix: conflicts

* chore: remove duplicates

* chore: remove duplicates

---------

Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
2023-04-12 07:38:35 +05:30
Frappe PR Bot
c93a5ab8f0 chore(release): Bumped to Version 13.49.12
## [13.49.12](https://github.com/frappe/erpnext/compare/v13.49.11...v13.49.12) (2023-04-11)

### Bug Fixes

* `payment entry is already created` on posawesome. (backport [#34712](https://github.com/frappe/erpnext/issues/34712)) ([#34753](https://github.com/frappe/erpnext/issues/34753)) ([b48fca3](b48fca3e5a))
* Allocate tax loss to tax account head on early payment discount ([#34287](https://github.com/frappe/erpnext/issues/34287)) ([92a26dd](92a26dda3c))
* asset monthly WDV and DD schedule [v13] ([#34645](https://github.com/frappe/erpnext/issues/34645)) ([fed43ae](fed43aeb85))
* BOM Update Cost, when no actual qty ([9725698](9725698b79))
* bom update log not working for large batch size ([9cf30d7](9cf30d7621))
* don't include cancelled JVs in assdeprledger report ([#34737](https://github.com/frappe/erpnext/issues/34737)) ([3007ac3](3007ac3c20))
* enclose ternary operator in parentheses ([198830a](198830a6c8))
* filter out old allocation's cf leaves while fetching leave details ([#34723](https://github.com/frappe/erpnext/issues/34723)) ([50de045](50de045247))
* format currency/float as per number format in work history ([#34545](https://github.com/frappe/erpnext/issues/34545)) ([892c480](892c480408))
* incorrect arg name in asset value adjustment ([545807a](545807a91e))
* incorrect balance qty in the stock ledger report ([dab1f1a](dab1f1a0d0))
* Item tax validity comparison fixes ([#34784](https://github.com/frappe/erpnext/issues/34784)) ([71bafab](71bafab41b))
* lost opportunity report issue ([#34626](https://github.com/frappe/erpnext/issues/34626)) ([ab06cb4](ab06cb42a3))
* posting time issue ([f22e777](f22e7775b3))
* provide filter by depreciable assets in fixed asset register ([#34803](https://github.com/frappe/erpnext/issues/34803)) ([8609bf4](8609bf4a12))
* serial no with zero quantity issue in stock reco ([46638b1](46638b19db))
* Shop by category fixes (backport [#34688](https://github.com/frappe/erpnext/issues/34688)) ([#34751](https://github.com/frappe/erpnext/issues/34751)) ([af828e4](af828e4554))

### Reverts

* Revert "fix: `payment entry is already created` on posawesome. (#34712)" ([034e35e](034e35e7f6)), closes [#34712](https://github.com/frappe/erpnext/issues/34712) [#34712](https://github.com/frappe/erpnext/issues/34712) [#34753](https://github.com/frappe/erpnext/issues/34753)
* remove frappe.send_message (v13) ([#34820](https://github.com/frappe/erpnext/issues/34820)) ([77f1322](77f1322732)), closes [#34816](https://github.com/frappe/erpnext/issues/34816)
2023-04-11 11:41:15 +00:00
Deepesh Garg
12cbe38299 Merge pull request #34812 from frappe/version-13-hotfix
chore: release v13
2023-04-11 17:09:06 +05:30
mergify[bot]
77f1322732 revert: remove frappe.send_message (v13) (#34820)
revert: remove frappe.send_message (v14) (#34816)

revert: remove frappe.send_message
(cherry picked from commit 8a331e0f26)

Co-authored-by: Ritwik Puri <ritwikpuri5678@gmail.com>
2023-04-11 16:22:51 +05:30
mergify[bot]
8609bf4a12 fix: provide filter by depreciable assets in fixed asset register (#34803)
fix: provide filter by depreciable assets in fixed asset register (#34803)

(cherry picked from commit c957a5cd2e)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-04-11 13:50:23 +05:30
mergify[bot]
71bafab41b fix: Item tax validity comparison fixes (#34784)
fix: Item tax validity comparison fixes (#34784)

fix: Item tax validity comparsion fixes
(cherry picked from commit 6f6928fa7b)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-10 12:39:44 +05:30
Anand Baburajan
ce151ddae4 Merge pull request #34780 from frappe/mergify/bp/version-13-hotfix/pr-34735
'Make Asset Movement' button translation fix in asset_list.js (backport #34735)
2023-04-07 15:34:32 +05:30
Hossein Yousefian
16ae117c97 'Make Asset Movement' button translation fix
(cherry picked from commit b70615ef18)
2023-04-07 10:01:19 +00:00
Frappe PR Bot
21cd789842 chore(release): Bumped to Version 13.49.11
## [13.49.11](https://github.com/frappe/erpnext/compare/v13.49.10...v13.49.11) (2023-04-05)

### Reverts

* Revert "fix: `payment entry is already created` on posawesome. (backport #34712)" (backport #34756) (#34757) ([98de1f2](98de1f201d)), closes [#34712](https://github.com/frappe/erpnext/issues/34712) [#34756](https://github.com/frappe/erpnext/issues/34756) [#34757](https://github.com/frappe/erpnext/issues/34757) [#34712](https://github.com/frappe/erpnext/issues/34712) [#34712](https://github.com/frappe/erpnext/issues/34712) [#34753](https://github.com/frappe/erpnext/issues/34753)
2023-04-05 11:45:33 +00:00
mergify[bot]
98de1f201d Revert "fix: payment entry is already created on posawesome. (backport #34712)" (backport #34756) (#34757)
Revert "fix: `payment entry is already created` on posawesome. (#34712)"

Revert "fix: `payment entry is already created` on posawesome. (backport #34712) (#34753)"

This reverts commit b48fca3e5a.

(cherry picked from commit 034e35e7f6)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-05 17:13:54 +05:30
Deepesh Garg
034e35e7f6 Revert "fix: payment entry is already created on posawesome. (#34712)"
Revert "fix: `payment entry is already created` on posawesome. (backport #34712) (#34753)"

This reverts commit b48fca3e5a.
2023-04-05 17:12:05 +05:30
mergify[bot]
b48fca3e5a fix: payment entry is already created on posawesome. (backport #34712) (#34753) 2023-04-05 13:55:33 +05:30
Frappe PR Bot
2c40be2337 chore: release v13 (#34732) 2023-04-05 13:48:11 +05:30
mergify[bot]
af828e4554 fix: Shop by category fixes (backport #34688) (#34751)
fix: Shop by category fixes (#34688)

* fix: Shop by category fixes

* chore: Update tests

(cherry picked from commit 56f5078357)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-05 13:09:49 +05:30
mergify[bot]
e76df6ff46 fix!: require sender and message for contact us page (#34707)
fix!: require sender and message for contact us page (#34707)

* fix: require sender and message for contact us page

* refactor: dont override frappe.send_message from client side

used override_whitelisted_method hook for the same

(cherry picked from commit f193393f57)

Co-authored-by: Ritwik Puri <ritwikpuri5678@gmail.com>
2023-04-05 12:46:56 +05:30
mergify[bot]
3007ac3c20 fix: don't include cancelled JVs in assdeprledger report (#34737)
fix: don't include cancelled JVs in assdeprledger report

(cherry picked from commit 3896d41e95)

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
2023-04-05 11:56:38 +05:30
mergify[bot]
92a26dda3c fix: Allocate tax loss to tax account head on early payment discount (#34287)
* fix: Taxes aren't discounted on early payment discount

- Deductions in payment entry must be split into income loss and tax loss
- Compute total discount in percentage, makes discounting different amounts proportionately easier

(cherry picked from commit 768c3a4927)

* fix: Recalculate difference amount after setting deductions

(cherry picked from commit 75ec0a0a85)

* fix: Set deductions in base currency

- Use field precision to get more accurate values

(cherry picked from commit dc2998f544)

* fix: Back update discounted amount in Invoice based on discount type

- Discount value was always trated as a percentage on back updation

(cherry picked from commit 2ae5834290)

* test: PE from SI with early payment discount amount & PE assertions in discount % test

(cherry picked from commit c217bb2018)

* fix: Set deduction amount in company currency on Doctype

- Even via JS, deductions amount is always in company currency
- Since there is nothing dynamic about this field, set it in the doctype spec itself
- fixed: Inconsistency between label currency and field currency formatted value

(cherry picked from commit 7f2e7badff)

* fix: Don't add to deductions if amount is 0

- misc: better docstring

(cherry picked from commit f02fc8acf0)

* fix: Paid amount must be discounted considering accounting currency

- Accounting is in the same currency if party currency and company currency is the same
- If accounting is in the same currency, paid and recvd amount is in the base currency
- Then, discount amount must also be in the base currency as it is deducted from paid amount
- Received amount must be in base currency if not multi currency
- cleanup: Deductions setting broken into smaller functions

(cherry picked from commit 761f68d7bf)

* fix: Multi-currency SI with base currency PE

- Return total discount loss in base currency
- Allocate payment based on terms: Set allocated amount in references table in base currency if accounting is in that currency
- Allocate payment based on terms: While back updating set paid amount (payment schedule) in transaction currency always
- minor: discount msgprint in correct currency

(cherry picked from commit b09c2381ca)

* test: Multi currency SI with multi-currency accounting and single currency accounting + Early payment discount

(cherry picked from commit 9abf0ef615)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/test_payment_entry.py

* fix: Handle rounding more gracefully

- Round off pending discount loss to avoid miniscule losses rounded to 0.0 that are added in deductions
- Use base amounts to calculate base losses instead of using conversion factor which increases rounding error
- Round of total base loss instead of individual income and tax losses to reduce rounding error
- Use default round off account for pending rounding loss in deductions

(cherry picked from commit caa1a3dccf)

* fix: Provision to apply early payment discount if payment is recorded late

- Party could have paid on time but payment is recorded late
- Prompt for reference date so that discount is applied while mapping
- Prompt only if discount in payment schedule of valid doctypes
- test: Reference date and impact on PE
- `make_payment_entry` (JS) must be able to access `this`

(cherry picked from commit d6d0163514)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.py
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
#	erpnext/buying/doctype/purchase_order/purchase_order.js
#	erpnext/public/js/controllers/transaction.js

* feat: Make Tax loss booking optional

- Checkbox in Accounts Settings
- Apply checkbox in PE deductions setting logic
- Adjust tests

(cherry picked from commit 216a46bd66)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json

* fix: Merge conflicts

* fix: 'Donation' does not have `company_currency` field

- Make sure check uses this field only for eligible documents

---------

Co-authored-by: marination <maricadsouza221197@gmail.com>
2023-04-05 11:55:22 +05:30
Anand Baburajan
fed43aeb85 fix: asset monthly WDV and DD schedule [v13] (#34645)
fix: monthly wdv and dd schedule
2023-04-05 11:45:15 +05:30
RJPvT
892c480408 fix: format currency/float as per number format in work history (#34545)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2023-04-04 11:08:55 +05:30
Rucha Mahabal
50de045247 fix: filter out old allocation's cf leaves while fetching leave details (#34723) 2023-04-03 20:19:11 +05:30
rohitwaghchaure
313aecf0ff Merge pull request #34720 from frappe/mergify/bp/version-13-hotfix/pr-34715
fix: bom update log not working for large batch size (backport #34715)
2023-04-03 16:55:37 +05:30
Rohit Waghchaure
9cf30d7621 fix: bom update log not working for large batch size
(cherry picked from commit d56070301c)
2023-04-03 10:20:07 +00:00
Sagar Sharma
e7fd47ae82 Merge pull request #34698 from frappe/mergify/bp/version-13-hotfix/pr-34656
fix: BOM Update Cost, when no actual qty (backport #34656)
2023-04-02 14:59:31 +05:30
s-aga-r
9725698b79 fix: BOM Update Cost, when no actual qty
(cherry picked from commit a4112c75c5)
2023-04-01 10:54:51 +00:00
ruthra kumar
008c985392 Merge pull request #34685 from frappe/mergify/bp/version-13-hotfix/pr-34679
fix: enclose ternary operator in parenthesis (backport #34679)
2023-03-31 14:05:16 +05:30
ruthra kumar
198830a6c8 fix: enclose ternary operator in parentheses
(cherry picked from commit 986daa6578)
2023-03-31 08:02:11 +00:00
Sagar Sharma
2770840946 Merge pull request #34682 from frappe/mergify/bp/version-13-hotfix/pr-34677
chore: make `Production Plan Item Reference` table hidden in Production Plan (backport #34677)
2023-03-31 12:43:26 +05:30
s-aga-r
45eb440b68 chore: conflicts 2023-03-31 12:42:51 +05:30
s-aga-r
85d8ed989d chore: make Production Plan Item Reference table hidden in Production Plan
(cherry picked from commit 706be2a415)

# Conflicts:
#	erpnext/manufacturing/doctype/production_plan/production_plan.json
2023-03-31 07:05:19 +00:00
rohitwaghchaure
6b026280c7 Merge pull request #34670 from frappe/mergify/bp/version-13-hotfix/pr-34664
fix: incorrect balance qty in the stock ledger report (backport #34664)
2023-03-30 18:17:51 +05:30
rohitwaghchaure
74280e0557 Merge pull request #34672 from frappe/mergify/bp/version-13-hotfix/pr-34636
fix: posting time issue (backport #34636)
2023-03-30 18:17:14 +05:30
Anand Baburajan
5f32696158 Merge pull request #34666 from frappe/mergify/bp/version-13-hotfix/pr-34661
chore: improve asset depr posting failure msg (backport #34661)
2023-03-30 17:50:32 +05:30
Rohit Waghchaure
f22e7775b3 fix: posting time issue
(cherry picked from commit 345e6facbe)
2023-03-30 11:56:41 +00:00
Rohit Waghchaure
dab1f1a0d0 fix: incorrect balance qty in the stock ledger report
(cherry picked from commit cbdaab940d)
2023-03-30 11:56:28 +00:00
Anand Baburajan
503c58edf8 chore: improve asset depr posting failure msg (#34661)
* chore: improve asset depr posting error msg

* chore: add period

* chore: improve msg

(cherry picked from commit d999dea3e4)
2023-03-30 11:09:34 +00:00
mergify[bot]
e54ff346ca chore: auto fill asset name and available for use date (backport #34660) (#34663)
* chore: auto fill asset name and available for use date

(cherry picked from commit af3e807607)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.json

* Update asset.json

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
2023-03-30 16:36:56 +05:30
Frappe PR Bot
278f38f2aa chore(release): Bumped to Version 13.49.10
## [13.49.10](https://github.com/frappe/erpnext/compare/v13.49.9...v13.49.10) (2023-03-30)

### Bug Fixes

* serial no with zero quantity issue in stock reco ([4cf66f0](4cf66f0585))
2023-03-30 08:48:15 +00:00
rohitwaghchaure
b1bb749e23 Merge pull request #34655 from frappe/mergify/bp/version-13/pr-34653
fix: serial no with zero quantity issue in stock reco (backport #34648) (backport #34653)
2023-03-30 14:15:59 +05:30
Rohit Waghchaure
4cf66f0585 fix: serial no with zero quantity issue in stock reco
(cherry picked from commit 17131e5a02)
(cherry picked from commit 46638b19db)
2023-03-30 08:10:43 +00:00
rohitwaghchaure
a872a7a9eb Merge pull request #34653 from frappe/mergify/bp/version-13-hotfix/pr-34648
fix: serial no with zero quantity issue in stock reco (backport #34648)
2023-03-30 13:39:28 +05:30
Anand Baburajan
60046feac3 Merge pull request #34651 from frappe/mergify/bp/version-13-hotfix/pr-34649
fix: incorrect arg name in asset value adjustment (backport #34649)
2023-03-30 13:10:07 +05:30
Rohit Waghchaure
46638b19db fix: serial no with zero quantity issue in stock reco
(cherry picked from commit 17131e5a02)
2023-03-30 07:35:38 +00:00
anandbaburajan
545807a91e fix: incorrect arg name in asset value adjustment
(cherry picked from commit 2b0470d1f5)
2023-03-30 07:34:09 +00:00
mergify[bot]
ab06cb42a3 fix: lost opportunity report issue (#34626)
fix: lost opportunity report issue (#34626)

* fix: lost opportunity report issue

* chore: Linting Issues

---------

Co-authored-by: Komal Saraf <komal@frappe.io>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit d0660ad222)

Co-authored-by: Komal-Saraf0609 <81952590+Komal-Saraf0609@users.noreply.github.com>
2023-03-29 17:04:40 +05:30
Bevan Tony Medrano
625b8e8005 Asset maintenance task add dropdown "3 Yearly" (#34607)
* feat(asset_maintenance.json):Add 3 yearly in periodicity dropdown

* add server side implications for 3 yearly
2023-03-29 16:59:49 +05:30
Frappe PR Bot
0da6237d22 chore(release): Bumped to Version 13.49.9
## [13.49.9](https://github.com/frappe/erpnext/compare/v13.49.8...v13.49.9) (2023-03-28)

### Bug Fixes

* `Blanket Order` (backport [#34279](https://github.com/frappe/erpnext/issues/34279)) ([#34548](https://github.com/frappe/erpnext/issues/34548)) ([8ddbac5](8ddbac5158))
* **client:** Amount calculation for 0 qty debit notes ([#34455](https://github.com/frappe/erpnext/issues/34455)) ([19dda80](19dda807d1))
* exchange gain/loss GL's should be removed if advance is cancelled ([#34529](https://github.com/frappe/erpnext/issues/34529)) ([00518eb](00518eb384))
* german translations ([#34312](https://github.com/frappe/erpnext/issues/34312)) ([661030a](661030aba1))
* incorrect `Opening Value` in `Stock Balance` report (backport [#34461](https://github.com/frappe/erpnext/issues/34461)) ([#34622](https://github.com/frappe/erpnext/issues/34622)) ([e53a96a](e53a96ae1d))
* incorrect depr schedules after asset repair [v13] ([#34520](https://github.com/frappe/erpnext/issues/34520)) ([ae88ba5](ae88ba5d18))
* Overallocation of 'qty' from Cr Notes to Parent Invoice ([d2a1acc](d2a1acc2e2))
* Party Name in SOA print when viewed from Customer/Supplier master ([#34597](https://github.com/frappe/erpnext/issues/34597)) ([4bdea43](4bdea436e3))
* Percentage billing in Sales Order ([#34606](https://github.com/frappe/erpnext/issues/34606)) ([3aab6e6](3aab6e6fa8))
* recalculate WDV rate after asset repair [v13] ([#34567](https://github.com/frappe/erpnext/issues/34567)) ([c8bde39](c8bde399e5))
* Search field not working for customer, supplier ([#32693](https://github.com/frappe/erpnext/issues/32693)) ([dbe289e](dbe289e734))
* unset address and contact on trash (backport [#34495](https://github.com/frappe/erpnext/issues/34495)) ([#34561](https://github.com/frappe/erpnext/issues/34561)) ([7f83d15](7f83d15bda))
* valuation rate issue while making stock entry from PO ([3574d49](3574d490db))
2023-03-28 18:17:57 +00:00
Deepesh Garg
526e350d98 Merge pull request #34610 from frappe/version-13-hotfix
chore: release v13
2023-03-28 23:46:31 +05:30
mergify[bot]
e53a96ae1d fix: incorrect Opening Value in Stock Balance report (backport #34461) (#34622)
* fix: incorrect `Opening Value` in `Stock Balance` report

(cherry picked from commit b04a101c11)

# Conflicts:
#	erpnext/stock/report/stock_balance/stock_balance.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-03-28 21:09:09 +05:30
mergify[bot]
3aab6e6fa8 fix: Percentage billing in Sales Order (#34606)
* fix: Percentage billing in Sales Order (#34606)

(cherry picked from commit 12ad2aa2e5)

# Conflicts:
#	erpnext/controllers/status_updater.py

* chore: resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-28 18:38:52 +05:30
Deepesh Garg
2e9e6eef05 chore: Update ubuntu version (#34620) 2023-03-28 17:25:33 +05:30
mergify[bot]
4bdea436e3 fix: Party Name in SOA print when viewed from Customer/Supplier master (#34597)
fix: Party Name in SOA print when viewed from Customer/Supplier master (#34597)

fix: Party Name in SOA print when viewd from Customer/Supplier master
(cherry picked from commit 50c1172f29)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-28 16:49:20 +05:30
mergify[bot]
dbe289e734 fix: Search field not working for customer, supplier (#32693)
* fix: searchfield not working for cuctsomer, supplier as per customize form

(cherry picked from commit 46d148defd)

* test: added test case to validate seachfields for customer, supplier

(cherry picked from commit 5f84993bae)

* fix: not able to select customer / supplier

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2023-03-28 16:06:27 +05:30
Frappe PR Bot
8c4f45307e chore(release): Bumped to Version 13.49.8
## [13.49.8](https://github.com/frappe/erpnext/compare/v13.49.7...v13.49.8) (2023-03-25)

### Bug Fixes

* valuation rate issue while making stock entry from PO ([78bd698](78bd698f9e))
2023-03-25 14:50:08 +00:00
rohitwaghchaure
7b2dc2449d Merge pull request #34590 from frappe/mergify/bp/version-13/pr-34555
fix: valuation rate issue while making stock entry from PO (backport #34555)
2023-03-25 20:18:27 +05:30
Rohit Waghchaure
78bd698f9e fix: valuation rate issue while making stock entry from PO
(cherry picked from commit 3574d490db)
2023-03-25 13:32:41 +00:00
Frappe PR Bot
544e37ca5c chore(release): Bumped to Version 13.49.7
## [13.49.7](https://github.com/frappe/erpnext/compare/v13.49.6...v13.49.7) (2023-03-23)

### Bug Fixes

* recalculate WDV rate after asset repair [v13] ([#34567](https://github.com/frappe/erpnext/issues/34567)) ([5f5fa84](5f5fa843ac))
2023-03-23 15:43:53 +00:00
Anand Baburajan
c7bdb1bbf9 Merge pull request #34572 from frappe/mergify/bp/version-13/pr-34567
fix: recalculate WDV rate after asset repair [v13] (backport #34567)
2023-03-23 21:11:48 +05:30
Anand Baburajan
5f5fa843ac fix: recalculate WDV rate after asset repair [v13] (#34567)
fix: recalculate wdv rate after asset repair
(cherry picked from commit c8bde399e5)
2023-03-23 14:28:08 +00:00
Anand Baburajan
c8bde399e5 fix: recalculate WDV rate after asset repair [v13] (#34567)
fix: recalculate wdv rate after asset repair
2023-03-23 19:44:13 +05:30
mergify[bot]
7f83d15bda fix: unset address and contact on trash (backport #34495) (#34561)
fix: unset address and contact on trash (#34495)

* fix(Customer): unset address and contact on trash

* fix(Supplier): unset address and contact on trash

---------

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
(cherry picked from commit f7bf1b8a0c)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-03-23 12:58:54 +05:30
rohitwaghchaure
ad5eb6da4e Merge pull request #34555 from rohitwaghchaure/dont-raise-error-while-mkaing-ste
fix: valuation rate issue while making stock entry from PO
2023-03-22 23:36:57 +05:30
Rohit Waghchaure
3574d490db fix: valuation rate issue while making stock entry from PO 2023-03-22 16:52:07 +05:30
Frappe PR Bot
f17b2de420 chore(release): Bumped to Version 13.49.6
## [13.49.6](https://github.com/frappe/erpnext/compare/v13.49.5...v13.49.6) (2023-03-22)

### Bug Fixes

* `Blanket Order` (backport [#34279](https://github.com/frappe/erpnext/issues/34279)) (backport [#34548](https://github.com/frappe/erpnext/issues/34548)) ([#34553](https://github.com/frappe/erpnext/issues/34553)) ([d5efeec](d5efeec0a4))
2023-03-22 09:29:45 +00:00
mergify[bot]
d5efeec0a4 fix: Blanket Order (backport #34279) (backport #34548) (#34553)
fix: `Blanket Order` (backport #34279) (#34548)

* fix: hide `+` button based on `Blanket Order Type`

(cherry picked from commit abf9a28d6a)

* feat: add field `Over Order Allowance (%)` in `Buying Settings`

(cherry picked from commit f5937f46cb)

# Conflicts:
#	erpnext/buying/doctype/buying_settings/buying_settings.json

* refactor: rewrite `blanket_order.py` queries in `QB`

(cherry picked from commit f3993783a3)

* fix: don't map item row having `0` qty

(cherry picked from commit fc1088d9c4)

* feat: consider `over_order_allowance` while validating order qty

(cherry picked from commit 8bcbc45add)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.py

* feat: add field `Over Order Allowance (%)` in `Selling Settings`

(cherry picked from commit d7da8928ac)

# Conflicts:
#	erpnext/selling/doctype/selling_settings/selling_settings.json

* feat: consider `over_order_allowance` while validating sales order qty

(cherry picked from commit 53701c37b1)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.py

* test: add test cases for `Over Order Allowance` against `Blanket Order`

(cherry picked from commit 66f650061d)

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
(cherry picked from commit 8ddbac5158)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-03-22 14:58:10 +05:30
mergify[bot]
8ddbac5158 fix: Blanket Order (backport #34279) (#34548)
* fix: hide `+` button based on `Blanket Order Type`

(cherry picked from commit abf9a28d6a)

* feat: add field `Over Order Allowance (%)` in `Buying Settings`

(cherry picked from commit f5937f46cb)

# Conflicts:
#	erpnext/buying/doctype/buying_settings/buying_settings.json

* refactor: rewrite `blanket_order.py` queries in `QB`

(cherry picked from commit f3993783a3)

* fix: don't map item row having `0` qty

(cherry picked from commit fc1088d9c4)

* feat: consider `over_order_allowance` while validating order qty

(cherry picked from commit 8bcbc45add)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.py

* feat: add field `Over Order Allowance (%)` in `Selling Settings`

(cherry picked from commit d7da8928ac)

# Conflicts:
#	erpnext/selling/doctype/selling_settings/selling_settings.json

* feat: consider `over_order_allowance` while validating sales order qty

(cherry picked from commit 53701c37b1)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.py

* test: add test cases for `Over Order Allowance` against `Blanket Order`

(cherry picked from commit 66f650061d)

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-03-22 13:50:29 +05:30
Frappe PR Bot
6e492ec514 chore: release v13 (#34531)
* chore: Update user manual link (#34478)

* chore: Update user manual link (#34478)

(cherry picked from commit be723bb9d4)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

* chore: Update version

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

* chore: Update version

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

---------

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

* fix: Overallocation of 'qty' from Cr Notes to Parent Invoice

Cr Notes 'qty' are overallocated to parent invoice, when there are
mulitple instances of same item in Invoice.

(cherry picked from commit e2f19c6a14)

* refactor: Ignore linked Cr Notes in Report output

(cherry picked from commit d0715a82eb)

* test: Gross Profit report output for Cr notes

2 New test cases added.
1. Standalone Cr notes will be reported as normal Invoices
2. Cr notes against an Invoice will not overallocate qty if there are
multiple instances of same item

(cherry picked from commit cc61daeec4)

* fix: incorrect depr schedules after asset repair [v13] (#34520)

* fix: incorrect schedule after repair for WDV and DD

* chore: only fix schedules for assets with calc_depr true

* fix: incorrect schedule after repair for straight line and manual

* refactor: calc depr in asset repair and if statement (#34526)

refactor: minor asset repair of calc depr and if statement

* fix(client): Amount calculation for 0 qty debit notes (#34455)

fix(client): Amount calculation for 0 qty debit notes (#34455)

fix(client): Amount calculaton for 0 qty debit notes

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
(cherry picked from commit ee6c107d58)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>

* fix: german translations (#34312)

fix: german translations (#34312)

fix: some german translations
(cherry picked from commit 59c2e7ec3e)

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

* fix: exchange gain/loss GL's should be removed if advance is cancelled (#34529)

* fix: report GL for invoice when advance has different exchange rate

If deferred revenue/expense is enabled for any item, don't repost.

* test: cancelling advance should remove exchange gain/loss

If there are no deferred revenue/expense cancelling advance should
cancel the exchange gain/loss booked due to different exchange rates
of payment and its linked invoice

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-03-21 18:20:16 +05:30
ruthra kumar
00518eb384 fix: exchange gain/loss GL's should be removed if advance is cancelled (#34529)
* fix: report GL for invoice when advance has different exchange rate

If deferred revenue/expense is enabled for any item, don't repost.

* test: cancelling advance should remove exchange gain/loss

If there are no deferred revenue/expense cancelling advance should
cancel the exchange gain/loss booked due to different exchange rates
of payment and its linked invoice
2023-03-21 16:16:36 +05:30
mergify[bot]
661030aba1 fix: german translations (#34312)
fix: german translations (#34312)

fix: some german translations
(cherry picked from commit 59c2e7ec3e)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-03-21 16:08:50 +05:30
mergify[bot]
19dda807d1 fix(client): Amount calculation for 0 qty debit notes (#34455)
fix(client): Amount calculation for 0 qty debit notes (#34455)

fix(client): Amount calculaton for 0 qty debit notes

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
(cherry picked from commit ee6c107d58)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-21 16:06:10 +05:30
Anand Baburajan
0d5abf1c95 refactor: calc depr in asset repair and if statement (#34526)
refactor: minor asset repair of calc depr and if statement
2023-03-21 14:30:28 +05:30
Anand Baburajan
ae88ba5d18 fix: incorrect depr schedules after asset repair [v13] (#34520)
* fix: incorrect schedule after repair for WDV and DD

* chore: only fix schedules for assets with calc_depr true

* fix: incorrect schedule after repair for straight line and manual
2023-03-21 12:35:44 +05:30
ruthra kumar
d855b532e4 Merge pull request #34511 from ruthra-kumar/manual_backport_of_34456
fix: Gross Profit reports Invoices with -ve qty for Invoices with Cr Notes (manual backport to version 13)
2023-03-20 15:42:09 +05:30
ruthra kumar
4e38e8da1b test: Gross Profit report output for Cr notes
2 New test cases added.
1. Standalone Cr notes will be reported as normal Invoices
2. Cr notes against an Invoice will not overallocate qty if there are
multiple instances of same item

(cherry picked from commit cc61daeec4)
2023-03-20 14:48:53 +05:30
ruthra kumar
6eeac48f17 refactor: Ignore linked Cr Notes in Report output
(cherry picked from commit d0715a82eb)
2023-03-20 14:48:53 +05:30
ruthra kumar
d2a1acc2e2 fix: Overallocation of 'qty' from Cr Notes to Parent Invoice
Cr Notes 'qty' are overallocated to parent invoice, when there are
mulitple instances of same item in Invoice.

(cherry picked from commit e2f19c6a14)
2023-03-20 14:48:53 +05:30
mergify[bot]
a3aa4d536a chore: Update user manual link (#34478)
* chore: Update user manual link (#34478)

(cherry picked from commit be723bb9d4)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

* chore: Update version

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

* chore: Update version

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

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-03-19 18:08:15 +05:30
157 changed files with 3714 additions and 1939 deletions

View File

@@ -14,7 +14,7 @@ concurrency:
jobs:
test:
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
timeout-minutes: 60
name: Patch Test

View File

@@ -18,7 +18,7 @@ concurrency:
jobs:
test:
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
timeout-minutes: 60
strategy:

View File

@@ -8,7 +8,7 @@ on:
jobs:
check_translation:
name: Translation Syntax Check
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Setup python3

View File

@@ -3,14 +3,13 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @anandbaburajan @deepeshgarg007
erpnext/erpnext_integrations/ @nextchamp-saqib
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/support/ @nextchamp-saqib @deepeshgarg007
pos* @nextchamp-saqib
erpnext/loan_management/ @deepeshgarg007
erpnext/regional @deepeshgarg007 @ruthra-kumar
erpnext/selling @deepeshgarg007 @ruthra-kumar
erpnext/support/ @deepeshgarg007
pos*
erpnext/buying/ @rohitwaghchaure @s-aga-r
erpnext/maintenance/ @rohitwaghchaure @s-aga-r
@@ -18,16 +17,10 @@ erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/controllers @deepeshgarg007 @rohitwaghchaure
erpnext/patches/ @deepeshgarg007 @rohitwaghchaure
requirements.txt @ankush
erpnext/healthcare/ @chillaranand
erpnext/hr/ @ruchamahabal
erpnext/non_profit/ @ruchamahabal
erpnext/payroll @ruchamahabal
erpnext/projects/ @ruchamahabal
.github/ @deepeshgarg007
pyproject.toml @ankush
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure
erpnext/public/ @nextchamp-saqib @marination
.github/ @ankush
requirements.txt @gavindsouza @ankush

View File

@@ -4,7 +4,7 @@ import frappe
from erpnext.hooks import regional_overrides
__version__ = "13.49.5"
__version__ = "13.52.2"
def get_default_company(user=None):

View File

@@ -297,7 +297,7 @@ def _make_test_records(verbose=None):
# fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
["_Test Depreciations", "Expenses", 0, None, None],
["_Test Depreciations", "Expenses", 0, "Depreciation", None],
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
# Receivable / Payable Account
["_Test Receivable", "Current Assets", 0, "Receivable", None],

View File

@@ -26,6 +26,7 @@
"determine_address_tax_category_from",
"column_break_19",
"add_taxes_from_item_tax_template",
"book_tax_discount_loss",
"period_closing_settings_section",
"acc_frozen_upto",
"frozen_accounts_modifier",
@@ -39,6 +40,7 @@
"submit_journal_entries",
"print_settings",
"show_inclusive_tax_in_print",
"show_taxes_as_table_in_print",
"column_break_12",
"show_payment_schedule_in_print",
"currency_exchange_section",
@@ -174,6 +176,7 @@
},
{
"default": "0",
"description": "Payment Terms from orders will be fetched into the invoices as is",
"fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check",
"label": "Automatically Fetch Payment Terms from Order"
@@ -284,6 +287,19 @@
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
"fieldtype": "Check",
"label": "Allow multi-currency invoices against single party account"
},
{
"default": "0",
"description": "Split Early Payment Discount Loss into Income and Tax Loss",
"fieldname": "book_tax_discount_loss",
"fieldtype": "Check",
"label": "Book Tax Loss on Early Payment Discount"
},
{
"default": "0",
"fieldname": "show_taxes_as_table_in_print",
"fieldtype": "Check",
"label": "Show Taxes as Table in Print"
}
],
"icon": "icon-cog",
@@ -291,7 +307,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-07-11 13:37:50.605141",
"modified": "2023-06-13 18:47:46.430291",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -56,7 +56,7 @@ class BankClearance(Document):
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date,
if(paid_from=%(account)s, paid_amount, 0) as credit,
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
if(paid_from=%(account)s, 0, received_amount) as debit,
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency

View File

@@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Asset', 'Asset Movement'];
},
refresh: function(frm) {

View File

@@ -75,6 +75,7 @@ class JournalEntry(AccountsController):
self.validate_empty_accounts_table()
self.set_account_and_party_balance()
self.validate_inter_company_accounts()
self.validate_depr_entry_voucher_type()
if self.docstatus == 0:
self.apply_tax_withholding()
@@ -134,6 +135,13 @@ class JournalEntry(AccountsController):
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
def validate_depr_entry_voucher_type(self):
if (
any(d.account_type == "Depreciation" for d in self.get("accounts"))
and self.voucher_type != "Depreciation Entry"
):
frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation"))
def validate_stock_accounts(self):
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
for account in stock_accounts:
@@ -237,25 +245,30 @@ class JournalEntry(AccountsController):
self.remove(d)
def update_asset_value(self):
if self.voucher_type != "Depreciation Entry":
if self.flags.planned_depr_entry or self.voucher_type != "Depreciation Entry":
return
processed_assets = []
for d in self.get("accounts"):
if (
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
d.reference_type == "Asset"
and d.reference_name
and d.account_type == "Depreciation"
and d.debit
):
processed_assets.append(d.reference_name)
asset = frappe.get_doc("Asset", d.reference_name)
if asset.calculate_depreciation:
continue
depr_value = d.debit or d.credit
asset.db_set("value_after_depreciation", asset.value_after_depreciation - depr_value)
fb_idx = 1
if self.finance_book:
for fb_row in asset.get("finance_books"):
if fb_row.finance_book == self.finance_book:
fb_idx = fb_row.idx
break
fb_row = asset.get("finance_books")[fb_idx - 1]
fb_row.value_after_depreciation -= d.debit
fb_row.db_update()
else:
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
asset.set_status()
@@ -317,38 +330,45 @@ class JournalEntry(AccountsController):
d.db_update()
def unlink_asset_reference(self):
if self.voucher_type != "Depreciation Entry":
return
processed_assets = []
for d in self.get("accounts"):
if (
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
self.voucher_type == "Depreciation Entry"
and d.reference_type == "Asset"
and d.reference_name
and d.account_type == "Depreciation"
and d.debit
):
processed_assets.append(d.reference_name)
asset = frappe.get_doc("Asset", d.reference_name)
if asset.calculate_depreciation:
fb_idx = None
for s in asset.get("schedules"):
if s.journal_entry == self.name:
s.db_set("journal_entry", None)
idx = cint(s.finance_book_id) or 1
finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation += s.depreciation_amount
finance_books.db_update()
asset.set_status()
fb_idx = cint(s.finance_book_id) or 1
break
if not fb_idx:
fb_idx = 1
if self.finance_book:
for fb_row in asset.get("finance_books"):
if fb_row.finance_book == self.finance_book:
fb_idx = fb_row.idx
break
fb_row = asset.get("finance_books")[fb_idx - 1]
fb_row.value_after_depreciation += d.debit
fb_row.db_update()
else:
depr_value = d.debit or d.credit
asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit)
asset.set_status()
elif self.voucher_type == "Journal Entry" and d.reference_type == "Asset" and d.reference_name:
journal_entry_for_scrap = frappe.db.get_value(
"Asset", d.reference_name, "journal_entry_for_scrap"
)
asset.db_set("value_after_depreciation", asset.value_after_depreciation + depr_value)
asset.set_status()
if journal_entry_for_scrap == self.name:
frappe.throw(
_("Journal Entry for Asset scrapping cannot be cancelled. Please restore the Asset.")
)
def unlink_inter_company_jv(self):
if (

View File

@@ -2,6 +2,21 @@
// For license information, please see license.txt
frappe.ui.form.on("Journal Entry Template", {
onload: function(frm) {
if(frm.is_new()) {
frappe.call({
type: "GET",
method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series",
callback: function(r){
if(r.message) {
frm.set_df_property("naming_series", "options", r.message.split("\n"));
frm.set_value("naming_series", r.message.split("\n")[0]);
frm.refresh_field("naming_series");
}
}
});
}
},
refresh: function(frm) {
frappe.model.set_default_values(frm.doc);
@@ -19,18 +34,6 @@ frappe.ui.form.on("Journal Entry Template", {
return { filters: filters };
});
frappe.call({
type: "GET",
method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series",
callback: function(r){
if(r.message){
frm.set_df_property("naming_series", "options", r.message.split("\n"));
frm.set_value("naming_series", r.message.split("\n")[0]);
frm.refresh_field("naming_series");
}
}
});
},
voucher_type: function(frm) {
var add_accounts = function(doc, r) {

View File

@@ -256,8 +256,6 @@ frappe.ui.form.on('Payment Entry', {
frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
party_account_currency, "references");
frm.set_currency_labels(["amount"], company_currency, "deductions");
cur_frm.set_df_property("source_exchange_rate", "description",
("1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency));
@@ -625,7 +623,7 @@ frappe.ui.form.on('Payment Entry', {
frm.events.set_unallocated_amount(frm);
},
get_outstanding_invoice: function(frm) {
get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) {
const today = frappe.datetime.get_today();
const fields = [
{fieldtype:"Section Break", label: __("Posting Date")},
@@ -655,12 +653,29 @@ frappe.ui.form.on('Payment Entry', {
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
];
let btn_text = "";
if (get_outstanding_invoices) {
btn_text = "Get Outstanding Invoices";
}
else if (get_orders_to_be_billed) {
btn_text = "Get Outstanding Orders";
}
frappe.prompt(fields, function(filters){
frappe.flags.allocate_payment_amount = true;
frm.events.validate_filters_data(frm, filters);
frm.doc.cost_center = filters.cost_center;
frm.events.get_outstanding_documents(frm, filters);
}, __("Filters"), __("Get Outstanding Documents"));
frm.events.get_outstanding_documents(frm, filters, get_outstanding_invoices, get_orders_to_be_billed);
}, __("Filters"), __(btn_text));
},
get_outstanding_invoices: function(frm) {
frm.events.get_outstanding_invoices_or_orders(frm, true, false);
},
get_outstanding_orders: function(frm) {
frm.events.get_outstanding_invoices_or_orders(frm, false, true);
},
validate_filters_data: function(frm, filters) {
@@ -686,7 +701,7 @@ frappe.ui.form.on('Payment Entry', {
}
},
get_outstanding_documents: function(frm, filters) {
get_outstanding_documents: function(frm, filters, get_outstanding_invoices, get_orders_to_be_billed) {
frm.clear_table("references");
if(!frm.doc.party) {
@@ -710,6 +725,13 @@ frappe.ui.form.on('Payment Entry', {
args[key] = filters[key];
}
if (get_outstanding_invoices) {
args["get_outstanding_invoices"] = true;
}
else if (get_orders_to_be_billed) {
args["get_orders_to_be_billed"] = true;
}
frappe.flags.allocate_payment_amount = filters['allocate_payment_amount'];
return frappe.call({

View File

@@ -48,7 +48,8 @@
"base_received_amount",
"base_received_amount_after_tax",
"section_break_14",
"get_outstanding_invoice",
"get_outstanding_invoices",
"get_outstanding_orders",
"references",
"section_break_34",
"total_allocated_amount",
@@ -353,12 +354,6 @@
"fieldtype": "Section Break",
"label": "Reference"
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_invoice",
"fieldtype": "Button",
"label": "Get Outstanding Invoice"
},
{
"fieldname": "references",
"fieldtype": "Table",
@@ -726,12 +721,24 @@
"fieldname": "section_break_60",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_invoices",
"fieldtype": "Button",
"label": "Get Outstanding Invoices"
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_orders",
"fieldtype": "Button",
"label": "Get Outstanding Orders"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-02-23 20:08:39.559814",
"modified": "2023-06-19 11:38:04.387219",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -7,7 +7,7 @@ from functools import reduce
import frappe
from frappe import ValidationError, _, scrub, throw
from frappe.utils import cint, comma_or, flt, getdate, nowdate
from frappe.utils import cint, comma_or, flt, get_link_to_form, getdate, nowdate
from six import iteritems, string_types
import erpnext
@@ -150,26 +150,98 @@ class PaymentEntry(AccountsController):
)
def validate_allocated_amount(self):
if self.payment_type == "Internal Transfer":
return
if self.party_type in ("Customer", "Supplier"):
self.validate_allocated_amount_with_latest_data()
else:
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
for d in self.get("references"):
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def validate_allocated_amount_with_latest_data(self):
latest_references = get_outstanding_reference_documents(
{
"posting_date": self.posting_date,
"company": self.company,
"party_type": self.party_type,
"payment_type": self.payment_type,
"party": self.party,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
}
)
# Group latest_references by (voucher_type, voucher_no)
latest_lookup = {}
for d in latest_references:
d = frappe._dict(d)
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
for d in self.get("references"):
if (flt(d.allocated_amount)) > 0:
if flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
)
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
# The reference has already been fully paid
if not latest:
frappe.throw(
_("{0} {1} has already been fully paid.").format(d.reference_doctype, d.reference_name)
)
# The reference has already been partly paid
elif (
latest.outstanding_amount < latest.invoice_amount
and flt(d.outstanding_amount, d.precision("outstanding_amount")) != latest.outstanding_amount
):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
).format(d.reference_doctype, d.reference_name)
)
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0:
if flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
)
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def delink_advance_entry_references(self):
for reference in self.references:
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
doc = frappe.get_doc(reference.reference_doctype, reference.reference_name)
repost_required = False
for adv_reference in doc.get("advances"):
if adv_reference.exchange_gain_loss != 0:
repost_required = True
break
if repost_required:
for item in doc.get("items"):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
frappe.msgprint(
_(
"Linked Invoice {0} has Exchange Gain/Loss GL entries due to this Payment. Submit a Journal manually to reverse its effects."
).format(get_link_to_form(doc.doctype, doc.name))
)
repost_required = False
doc.delink_advance_entries(self.name)
if repost_required:
doc.reload()
doc.docstatus = 2
doc.make_gl_entries()
doc.docstatus = 1
doc.make_gl_entries()
def set_missing_values(self):
if self.payment_type == "Internal Transfer":
for field in (
@@ -368,7 +440,7 @@ class PaymentEntry(AccountsController):
for k, v in no_oustanding_refs.items():
frappe.msgprint(
_(
"{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry."
"{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry."
).format(
_(k),
frappe.bold(", ".join(d.reference_name for d in v)),
@@ -417,7 +489,7 @@ class PaymentEntry(AccountsController):
for ref in self.get("references"):
if ref.payment_term and ref.reference_name:
key = (ref.payment_term, ref.reference_name)
key = (ref.payment_term, ref.reference_name, ref.reference_doctype)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += ref.allocated_amount
@@ -425,20 +497,37 @@ class PaymentEntry(AccountsController):
payment_schedule = frappe.get_all(
"Payment Schedule",
filters={"parent": ref.reference_name},
fields=["paid_amount", "payment_amount", "payment_term", "discount", "outstanding"],
fields=[
"paid_amount",
"payment_amount",
"payment_term",
"discount",
"outstanding",
"discount_type",
],
)
for term in payment_schedule:
invoice_key = (term.payment_term, ref.reference_name)
invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
term.discount / 100
)
if not (term.discount_type and term.discount):
continue
if term.discount_type == "Percentage":
invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
term.discount / 100
)
else:
invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount
for idx, (key, allocated_amount) in enumerate(iteritems(invoice_payment_amount_map), 1):
if not invoice_paid_amount_map.get(key):
frappe.throw(_("Payment term {0} not used in {1}").format(key[0], key[1]))
allocated_amount = self.get_allocated_amount_in_transaction_currency(
allocated_amount, key[2], key[1]
)
outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))
@@ -473,6 +562,33 @@ class PaymentEntry(AccountsController):
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
)
def get_allocated_amount_in_transaction_currency(
self, allocated_amount, reference_doctype, reference_docname
):
"""
Payment Entry could be in base currency while reference's payment schedule
is always in transaction currency.
E.g.
* SI with base=INR and currency=USD
* SI with payment schedule in USD
* PE in INR (accounting done in base currency)
"""
ref_currency, ref_exchange_rate = frappe.db.get_value(
reference_doctype, reference_docname, ["currency", "conversion_rate"]
)
is_single_currency = self.paid_from_account_currency == self.paid_to_account_currency
# PE in different currency
reference_is_multi_currency = self.paid_from_account_currency != ref_currency
if not (is_single_currency and reference_is_multi_currency):
return allocated_amount
allocated_amount = flt(
allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount")
)
return allocated_amount
def set_status(self):
if self.docstatus == 2:
self.status = "Cancelled"
@@ -1251,32 +1367,48 @@ def get_outstanding_reference_documents(args):
if args.get("company"):
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
args.get("company"),
filters=args,
condition=condition,
)
outstanding_invoices = []
negative_outstanding_invoices = []
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
if args.get("get_outstanding_invoices"):
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
args.get("company"),
filters=args,
condition=condition,
)
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
party_account_currency, company_currency, d.posting_date
)
if d.voucher_type in ("Purchase Invoice"):
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
party_account_currency, company_currency, d.posting_date
)
if d.voucher_type in ("Purchase Invoice"):
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
# Get negative outstanding sales /purchase invoices
negative_outstanding_invoices = []
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
party_account_currency,
company_currency,
condition=condition,
)
# Get all SO / PO which are not fully billed or against which full advance not paid
orders_to_be_billed = []
if args.get("party_type") != "Student":
if args.get("get_orders_to_be_billed") and args.get("party_type") != "Student":
orders_to_be_billed = get_orders_to_be_billed(
args.get("posting_date"),
args.get("party_type"),
@@ -1287,25 +1419,22 @@ def get_outstanding_reference_documents(args):
filters=args,
)
# Get negative outstanding sales /purchase invoices
negative_outstanding_invoices = []
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
party_account_currency,
company_currency,
condition=condition,
)
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
if not data:
if args.get("get_outstanding_invoices") and args.get("get_orders_to_be_billed"):
ref_document_type = "invoices or orders"
elif args.get("get_outstanding_invoices"):
ref_document_type = "invoices"
elif args.get("get_orders_to_be_billed"):
ref_document_type = "orders"
frappe.msgprint(
_(
"No outstanding invoices found for the {0} {1} which qualify the filters you have specified."
).format(_(args.get("party_type")).lower(), frappe.bold(args.get("party")))
"No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
).format(
ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
)
)
return data
@@ -1390,7 +1519,7 @@ def get_orders_to_be_billed(
if voucher_type:
doc = frappe.get_doc({"doctype": voucher_type})
condition = ""
if doc and hasattr(doc, "cost_center"):
if doc and hasattr(doc, "cost_center") and doc.cost_center:
condition = " and cost_center='%s'" % cost_center
orders = []
@@ -1436,9 +1565,15 @@ def get_orders_to_be_billed(
order_list = []
for d in orders:
if not (
flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))
if (
filters
and filters.get("outstanding_amt_greater_than")
and filters.get("outstanding_amt_less_than")
and not (
flt(filters.get("outstanding_amt_greater_than"))
<= flt(d.outstanding_amount)
<= flt(filters.get("outstanding_amt_less_than"))
)
):
continue
@@ -1778,10 +1913,20 @@ def get_bill_no_and_update_amounts(
@frappe.whitelist()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
def get_payment_entry(
dt,
dn,
party_amount=None,
bank_account=None,
bank_amount=None,
reference_date=None,
):
reference_doc = None
doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (
100.0 + over_billing_allowance
):
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
party_type = set_party_type(dt)
@@ -1799,8 +1944,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
)
paid_amount, received_amount, discount_amount = apply_early_payment_discount(
paid_amount, received_amount, doc
reference_date = getdate(reference_date)
paid_amount, received_amount, discount_amount, valid_discounts = apply_early_payment_discount(
paid_amount, received_amount, doc, party_account_currency, reference_date
)
pe = frappe.new_doc("Payment Entry")
@@ -1808,6 +1954,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.company = doc.company
pe.cost_center = doc.get("cost_center")
pe.posting_date = nowdate()
pe.reference_date = reference_date
pe.mode_of_payment = doc.get("mode_of_payment")
pe.party_type = party_type
pe.party = doc.get(scrub(party_type))
@@ -1841,14 +1988,19 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
frappe.msgprint(_("{0} is on hold till {1}").format(doc.name, doc.release_date))
else:
if doc.doctype in ("Sales Invoice", "Purchase Invoice") and frappe.get_value(
if doc.doctype in (
"Sales Invoice",
"Purchase Invoice",
"Purchase Order",
"Sales Order",
) and frappe.get_value(
"Payment Terms Template",
{"name": doc.payment_terms_template},
"allocate_payment_based_on_payment_terms",
):
for reference in get_reference_as_per_payment_terms(
doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount
doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
):
pe.append("references", reference)
else:
@@ -1899,16 +2051,17 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
reference_doc = doc
pe.set_exchange_rate(ref_doc=reference_doc)
pe.set_amounts()
if discount_amount:
pe.set_gain_or_loss(
account_details={
"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
"cost_center": pe.cost_center
or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": discount_amount * (-1 if payment_type == "Pay" else 1),
}
base_total_discount_loss = 0
if frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss"):
base_total_discount_loss = split_early_payment_discount_loss(pe, doc, valid_discounts)
set_pending_discount_loss(
pe, doc, discount_amount, base_total_discount_loss, party_account_currency
)
pe.set_difference_amount()
pe.set_difference_amount()
return pe
@@ -2044,20 +2197,30 @@ def set_paid_amount_and_received_amount(
return paid_amount, received_amount
def apply_early_payment_discount(paid_amount, received_amount, doc):
def apply_early_payment_discount(
paid_amount, received_amount, doc, party_account_currency, reference_date
):
total_discount = 0
valid_discounts = []
eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
has_payment_schedule = hasattr(doc, "payment_schedule") and doc.payment_schedule
if doc.doctype in eligible_for_payments and has_payment_schedule:
# Non eligible documents may not have `company_currency` field
is_multi_currency = party_account_currency != doc.company_currency
for term in doc.payment_schedule:
if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
if not term.discounted_amount and term.discount and reference_date <= term.discount_date:
if term.discount_type == "Percentage":
discount_amount = flt(doc.get("grand_total")) * (term.discount / 100)
grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
discount_amount = flt(grand_total) * (term.discount / 100)
else:
discount_amount = term.discount
discount_amount_in_foreign_currency = discount_amount * doc.get("conversion_rate", 1)
# if accounting is done in the same currency, paid_amount = received_amount
conversion_rate = doc.get("conversion_rate", 1) if is_multi_currency else 1
discount_amount_in_foreign_currency = discount_amount * conversion_rate
if doc.doctype == "Sales Invoice":
paid_amount -= discount_amount
@@ -2066,23 +2229,151 @@ def apply_early_payment_discount(paid_amount, received_amount, doc):
received_amount -= discount_amount
paid_amount -= discount_amount_in_foreign_currency
valid_discounts.append({"type": term.discount_type, "discount": term.discount})
total_discount += discount_amount
if total_discount:
money = frappe.utils.fmt_money(total_discount, currency=doc.get("currency"))
currency = doc.get("currency") if is_multi_currency else doc.company_currency
money = frappe.utils.fmt_money(total_discount, currency=currency)
frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
return paid_amount, received_amount, total_discount
return paid_amount, received_amount, total_discount, valid_discounts
def set_pending_discount_loss(
pe, doc, discount_amount, base_total_discount_loss, party_account_currency
):
# If multi-currency, get base discount amount to adjust with base currency deductions/losses
if party_account_currency != doc.company_currency:
discount_amount = discount_amount * doc.get("conversion_rate", 1)
# Avoid considering miniscule losses
discount_amount = flt(discount_amount - base_total_discount_loss, doc.precision("grand_total"))
# Set base discount amount (discount loss/pending rounding loss) in deductions
if discount_amount > 0.0:
positive_negative = -1 if pe.payment_type == "Pay" else 1
# If tax loss booking is enabled, pending loss will be rounding loss.
# Otherwise it will be the total discount loss.
book_tax_loss = frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss")
account_type = "round_off_account" if book_tax_loss else "default_discount_account"
pe.set_gain_or_loss(
account_details={
"account": frappe.get_cached_value("Company", pe.company, account_type),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": discount_amount * positive_negative,
}
)
def split_early_payment_discount_loss(pe, doc, valid_discounts) -> float:
"""Split early payment discount into Income Loss & Tax Loss."""
total_discount_percent = get_total_discount_percent(doc, valid_discounts)
if not total_discount_percent:
return 0.0
base_loss_on_income = add_income_discount_loss(pe, doc, total_discount_percent)
base_loss_on_taxes = add_tax_discount_loss(pe, doc, total_discount_percent)
# Round off total loss rather than individual losses to reduce rounding error
return flt(base_loss_on_income + base_loss_on_taxes, doc.precision("grand_total"))
def get_total_discount_percent(doc, valid_discounts) -> float:
"""Get total percentage and amount discount applied as a percentage."""
total_discount_percent = (
sum(
discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage"
)
or 0.0
)
# Operate in percentages only as it makes the income & tax split easier
total_discount_amount = (
sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Amount")
or 0.0
)
if total_discount_amount:
discount_percentage = (total_discount_amount / doc.get("grand_total")) * 100
total_discount_percent += discount_percentage
return total_discount_percent
return total_discount_percent
def add_income_discount_loss(pe, doc, total_discount_percent) -> float:
"""Add loss on income discount in base currency."""
precision = doc.precision("total")
base_loss_on_income = doc.get("base_total") * (total_discount_percent / 100)
pe.append(
"deductions",
{
"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(base_loss_on_income, precision),
},
)
return base_loss_on_income # Return loss without rounding
def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
"""Add loss on tax discount in base currency."""
tax_discount_loss = {}
base_total_tax_loss = 0
precision = doc.precision("tax_amount_after_discount_amount", "taxes")
# The same account head could be used more than once
for tax in doc.get("taxes", []):
base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * (
total_discount_percentage / 100
)
account = tax.get("account_head")
if not tax_discount_loss.get(account):
tax_discount_loss[account] = base_tax_loss
else:
tax_discount_loss[account] += base_tax_loss
for account, loss in tax_discount_loss.items():
base_total_tax_loss += loss
if loss == 0.0:
continue
pe.append(
"deductions",
{
"account": account,
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(loss, precision),
},
)
return base_total_tax_loss # Return loss without rounding
def get_reference_as_per_payment_terms(
payment_schedule, dt, dn, doc, grand_total, outstanding_amount
payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
):
references = []
is_multi_currency_acc = (doc.currency != doc.company_currency) and (
party_account_currency != doc.company_currency
)
for payment_term in payment_schedule:
payment_term_outstanding = flt(
payment_term.payment_amount - payment_term.paid_amount, payment_term.precision("payment_amount")
)
if not is_multi_currency_acc:
# If accounting is done in company currency for multi-currency transaction
payment_term_outstanding = flt(
payment_term_outstanding * doc.get("conversion_rate"), payment_term.precision("payment_amount")
)
if payment_term_outstanding:
references.append(

View File

@@ -5,6 +5,7 @@ import unittest
import frappe
from frappe import qb
from frappe.tests.utils import change_settings
from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import (
@@ -252,10 +253,25 @@ class TestPaymentEntry(unittest.TestCase):
},
)
si.save()
si.submit()
frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1)
pe_with_tax_loss = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
self.assertEqual(pe_with_tax_loss.references[0].payment_term, "30 Credit Days with 10% Discount")
self.assertEqual(pe_with_tax_loss.references[0].allocated_amount, 236.0)
self.assertEqual(pe_with_tax_loss.paid_amount, 212.4)
self.assertEqual(pe_with_tax_loss.deductions[0].amount, 20.0) # Loss on Income
self.assertEqual(pe_with_tax_loss.deductions[1].amount, 3.6) # Loss on Tax
self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC")
frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
self.assertEqual(pe.references[0].allocated_amount, 236.0)
self.assertEqual(pe.paid_amount, 212.4)
self.assertEqual(pe.deductions[0].amount, 23.6)
pe.submit()
si.load_from_db()
@@ -265,6 +281,190 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(si.payment_schedule[0].outstanding, 0)
self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
def test_payment_entry_against_payment_terms_with_discount_amount(self):
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
si.payment_terms_template = "Test Discount Amount Template"
create_payment_terms_template_with_discount(
name="30 Credit Days with Rs.50 Discount",
discount_type="Amount",
discount=50,
template_name="Test Discount Amount Template",
)
frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
si.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 18,
},
)
si.save()
si.submit()
# Set reference date past discount cut off date
pe_1 = get_payment_entry(
"Sales Invoice",
si.name,
bank_account="_Test Cash - _TC",
reference_date=frappe.utils.add_days(si.posting_date, 2),
)
self.assertEqual(pe_1.paid_amount, 236.0) # discount not applied
# Test if tax loss is booked on enabling configuration
frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1)
pe_with_tax_loss = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
self.assertEqual(pe_with_tax_loss.deductions[0].amount, 42.37) # Loss on Income
self.assertEqual(pe_with_tax_loss.deductions[1].amount, 7.63) # Loss on Tax
self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC")
frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
self.assertEqual(pe.references[0].allocated_amount, 236.0)
self.assertEqual(pe.paid_amount, 186)
self.assertEqual(pe.deductions[0].amount, 50.0)
pe.submit()
si.load_from_db()
self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
self.assertEqual(si.payment_schedule[0].paid_amount, 186)
self.assertEqual(si.payment_schedule[0].outstanding, 0)
self.assertEqual(si.payment_schedule[0].discounted_amount, 50)
@change_settings(
"Accounts Settings",
{
"allow_multi_currency_invoices_against_single_party_account": 1,
"book_tax_discount_loss": 1,
},
)
def test_payment_entry_multicurrency_si_with_base_currency_accounting_early_payment_discount(
self,
):
"""
1. Multi-currency SI with single currency accounting (company currency)
2. PE with early payment discount
3. Test if Paid Amount is calculated in company currency
4. Test if deductions are calculated in company currency
SI is in USD to document agreed amounts that are in USD, but the accounting is in base currency.
"""
si = create_sales_invoice(
customer="_Test Customer",
currency="USD",
conversion_rate=50,
do_not_save=1,
)
create_payment_terms_template_with_discount()
si.payment_terms_template = "Test Discount Template"
frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
si.save()
si.submit()
pe = get_payment_entry(
"Sales Invoice",
si.name,
bank_account="_Test Bank - _TC",
)
pe.reference_no = si.name
pe.reference_date = nowdate()
# Early payment discount loss on income
self.assertEqual(pe.paid_amount, 4500.0) # Amount in company currency
self.assertEqual(pe.received_amount, 4500.0)
self.assertEqual(pe.deductions[0].amount, 500.0)
self.assertEqual(pe.deductions[0].account, "Write Off - _TC")
self.assertEqual(pe.difference_amount, 0.0)
pe.insert()
pe.submit()
expected_gle = dict(
(d[0], d)
for d in [
["Debtors - _TC", 0, 5000, si.name],
["_Test Bank - _TC", 4500, 0, None],
["Write Off - _TC", 500.0, 0, None],
]
)
self.validate_gl_entries(pe.name, expected_gle)
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 0)
def test_payment_entry_multicurrency_accounting_si_with_early_payment_discount(self):
"""
1. Multi-currency SI with multi-currency accounting
2. PE with early payment discount and also exchange loss
3. Test if Paid Amount is calculated in transaction currency
4. Test if deductions are calculated in base/company currency
5. Test if exchange loss is reflected in difference
"""
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
do_not_save=1,
)
create_payment_terms_template_with_discount()
si.payment_terms_template = "Test Discount Template"
frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
si.save()
si.submit()
pe = get_payment_entry(
"Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700
)
pe.reference_no = si.name
pe.reference_date = nowdate()
# Early payment discount loss on income
self.assertEqual(pe.paid_amount, 90.0)
self.assertEqual(pe.received_amount, 4200.0) # 5000 - 500 (discount) - 300 (exchange loss)
self.assertEqual(pe.deductions[0].amount, 500.0)
self.assertEqual(pe.deductions[0].account, "Write Off - _TC")
# Exchange loss
self.assertEqual(pe.difference_amount, 300.0)
pe.append(
"deductions",
{
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 300.0,
},
)
pe.insert()
pe.submit()
self.assertEqual(pe.difference_amount, 0.0)
expected_gle = dict(
(d[0], d)
for d in [
["_Test Receivable USD - _TC", 0, 5000, si.name],
["_Test Bank - _TC", 4200, 0, None],
["Write Off - _TC", 500.0, 0, None],
["_Test Exchange Gain/Loss - _TC", 300.0, 0, None],
]
)
self.validate_gl_entries(pe.name, expected_gle)
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 0)
def test_payment_against_purchase_invoice_to_check_status(self):
pi = make_purchase_invoice(
supplier="_Test Supplier USD",
@@ -799,6 +999,30 @@ class TestPaymentEntry(unittest.TestCase):
self.assertTrue("is on hold" in str(err.exception).lower())
def test_duplicate_payment_entry_allocate_amount(self):
si = create_sales_invoice()
pe_draft = get_payment_entry("Sales Invoice", si.name)
pe_draft.insert()
pe = get_payment_entry("Sales Invoice", si.name)
pe.submit()
self.assertRaises(frappe.ValidationError, pe_draft.submit)
def test_duplicate_payment_entry_partial_allocate_amount(self):
si = create_sales_invoice()
pe_draft = get_payment_entry("Sales Invoice", si.name)
pe_draft.insert()
pe = get_payment_entry("Sales Invoice", si.name)
pe.received_amount = si.total / 2
pe.references[0].allocated_amount = si.total / 2
pe.submit()
self.assertRaises(frappe.ValidationError, pe_draft.submit)
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")
@@ -856,24 +1080,27 @@ def create_payment_terms_template():
).insert()
def create_payment_terms_template_with_discount():
def create_payment_terms_template_with_discount(
name=None, discount_type=None, discount=None, template_name=None
):
create_payment_term(name or "30 Credit Days with 10% Discount")
template_name = template_name or "Test Discount Template"
create_payment_term("30 Credit Days with 10% Discount")
if not frappe.db.exists("Payment Terms Template", "Test Discount Template"):
payment_term_template = frappe.get_doc(
if not frappe.db.exists("Payment Terms Template", template_name):
frappe.get_doc(
{
"doctype": "Payment Terms Template",
"template_name": "Test Discount Template",
"template_name": template_name,
"allocate_payment_based_on_payment_terms": 1,
"terms": [
{
"doctype": "Payment Terms Template Detail",
"payment_term": "30 Credit Days with 10% Discount",
"payment_term": name or "30 Credit Days with 10% Discount",
"invoice_portion": 100,
"credit_days_based_on": "Day(s) after invoice date",
"credit_days": 2,
"discount": 10,
"discount_type": discount_type or "Percentage",
"discount": discount or 10,
"discount_validity_based_on": "Day(s) after invoice date",
"discount_validity": 1,
}

View File

@@ -3,6 +3,7 @@
"creation": "2016-06-15 15:56:30.815503",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"account",
"cost_center",
@@ -17,9 +18,7 @@
"in_list_view": 1,
"label": "Account",
"options": "Account",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
"reqd": 1
},
{
"fieldname": "cost_center",
@@ -28,37 +27,30 @@
"label": "Cost Center",
"options": "Cost Center",
"print_hide": 1,
"reqd": 1,
"show_days": 1,
"show_seconds": 1
"reqd": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
"label": "Amount (Company Currency)",
"options": "Company:company:default_currency",
"reqd": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
"fieldtype": "Column Break"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description",
"show_days": 1,
"show_seconds": 1
"label": "Description"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-09-12 20:38:08.110674",
"modified": "2023-03-06 07:11:57.739619",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Deduction",
@@ -66,5 +58,6 @@
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": []
}

View File

@@ -42,7 +42,7 @@ frappe.ui.form.on("Payment Request", "refresh", function(frm) {
});
}
if(!frm.doc.payment_gateway_account && frm.doc.status == "Initiated") {
if((!frm.doc.payment_gateway_account || frm.doc.payment_request_type == "Outward") && frm.doc.status == "Initiated") {
frm.add_custom_button(__('Create Payment Entry'), function(){
frappe.call({
method: "erpnext.accounts.doctype.payment_request.payment_request.make_payment_entry",

View File

@@ -254,6 +254,7 @@ class PaymentRequest(Document):
payment_entry.update(
{
"mode_of_payment": self.mode_of_payment,
"reference_no": self.name,
"reference_date": nowdate(),
"remarks": "Payment Entry against {0} {1} via Payment Request {2}".format(
@@ -403,25 +404,22 @@ def make_payment_request(**args):
else ""
)
existing_payment_request = None
if args.order_type == "Shopping Cart":
existing_payment_request = frappe.db.get_value(
"Payment Request",
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": ("!=", 2)},
)
draft_payment_request = frappe.db.get_value(
"Payment Request",
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0},
)
if existing_payment_request:
existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn)
if existing_payment_request_amount:
grand_total -= existing_payment_request_amount
if draft_payment_request:
frappe.db.set_value(
"Payment Request", existing_payment_request, "grand_total", grand_total, update_modified=False
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
)
pr = frappe.get_doc("Payment Request", existing_payment_request)
pr = frappe.get_doc("Payment Request", draft_payment_request)
else:
if args.order_type != "Shopping Cart":
existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn)
if existing_payment_request_amount:
grand_total -= existing_payment_request_amount
pr = frappe.new_doc("Payment Request")
pr.update(
{

View File

@@ -169,21 +169,18 @@ class PeriodClosingVoucher(AccountsController):
return frappe.db.sql(
"""
select
t2.account_currency,
t1.account_currency,
{dimension_fields},
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2
from `tabGL Entry` t1
where
t1.is_cancelled = 0
and t1.account = t2.name
and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2
and t2.company = %s
and t1.account in (select name from `tabAccount` where report_type = 'Profit and Loss' and docstatus < 2 and company = %s)
and t1.posting_date between %s and %s
group by {dimension_fields}
""".format(
dimension_fields=", ".join(dimension_fields)
dimension_fields=", ".join(dimension_fields),
),
(self.company, self.get("year_start_date"), self.posting_date),
as_dict=1,

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
from six import iteritems
@@ -675,18 +676,22 @@ def get_bin_qty(item_code, warehouse):
def get_pos_reserved_qty(item_code, warehouse):
reserved_qty = frappe.db.sql(
"""select sum(p_item.qty) as qty
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
where p.name = p_item.parent
and ifnull(p.consolidated_invoice, '') = ''
and p_item.docstatus = 1
and p_item.item_code = %s
and p_item.warehouse = %s
""",
(item_code, warehouse),
as_dict=1,
)
p_inv = frappe.qb.DocType("POS Invoice")
p_item = frappe.qb.DocType("POS Invoice Item")
reserved_qty = (
frappe.qb.from_(p_inv)
.from_(p_item)
.select(Sum(p_item.qty).as_("qty"))
.where(
(p_inv.name == p_item.parent)
& (IfNull(p_inv.consolidated_invoice, "") == "")
& (p_inv.is_return == 0)
& (p_item.docstatus == 1)
& (p_item.item_code == item_code)
& (p_item.warehouse == warehouse)
)
).run(as_dict=True)
return reserved_qty[0].qty or 0 if reserved_qty else 0

View File

@@ -81,8 +81,12 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
}
if(doc.docstatus == 1 && doc.outstanding_amount != 0
&& !(doc.is_return && doc.return_against)) {
this.frm.add_custom_button(__('Payment'), this.make_payment_entry, __('Create'));
&& !(doc.is_return && doc.return_against) && !doc.on_hold) {
this.frm.add_custom_button(
__('Payment'),
() => this.make_payment_entry(),
__('Create')
);
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
}
@@ -299,7 +303,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
apply_tds: function(frm) {
var me = this;
me.frm.set_value("tax_withheld_vouchers", []);
if (!me.frm.doc.apply_tds) {
me.frm.set_value("tax_withholding_category", '');
me.frm.set_df_property("tax_withholding_category", "hidden", 1);

View File

@@ -176,6 +176,7 @@
"fieldname": "received_qty",
"fieldtype": "Float",
"label": "Received Qty",
"no_copy": 1,
"read_only": 1
},
{
@@ -872,7 +873,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-10-12 03:37:29.032732",
"modified": "2023-07-02 18:39:41.495723",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -75,9 +75,12 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
if (doc.docstatus == 1 && doc.outstanding_amount!=0
&& !(cint(doc.is_return) && doc.return_against)) {
cur_frm.add_custom_button(__('Payment'),
this.make_payment_entry, __('Create'));
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
this.frm.add_custom_button(
__('Payment'),
() => this.make_payment_entry(),
__('Create')
);
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
}
if(doc.docstatus==1 && !doc.is_return) {
@@ -313,6 +316,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
make_inter_company_invoice: function() {
let me = this;
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_inter_company_purchase_invoice",
frm: me.frm
@@ -649,19 +653,6 @@ frappe.ui.form.on('Sales Invoice', {
}
}
// expense account
frm.fields_dict['items'].grid.get_field('expense_account').get_query = function(doc) {
if (erpnext.is_perpetual_inventory_enabled(doc.company)) {
return {
filters: {
'report_type': 'Profit and Loss',
'company': doc.company,
"is_group": 0
}
}
}
}
// discount account
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
return {

View File

@@ -1107,7 +1107,7 @@ class SalesInvoice(SellingController):
if self.is_return:
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
asset, item.base_net_amount, item.finance_book
asset, item.base_net_amount, item.finance_book, self.posting_date
)
asset.db_set("disposal_date", None)
@@ -1122,7 +1122,7 @@ class SalesInvoice(SellingController):
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset, item.base_net_amount, item.finance_book
asset, item.base_net_amount, item.finance_book, self.posting_date
)
asset.db_set("disposal_date", self.posting_date)

View File

@@ -3470,6 +3470,78 @@ class TestSalesInvoice(unittest.TestCase):
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
)
def test_gain_loss_on_advance_cancellation(self):
unlink_enabled = frappe.db.get_single_value(
"Accounts Settings", "unlink_payment_on_cancellation_of_invoice"
)
frappe.db.set_single_value("Accounts Settings", "unlink_payment_on_cancellation_of_invoice", 1)
pe = frappe.get_doc(
{
"doctype": "Payment Entry",
"payment_type": "Receive",
"party_type": "Customer",
"party": "_Test Customer USD",
"company": "_Test Company",
"paid_from_account_currency": "USD",
"paid_to_account_currency": "INR",
"source_exchange_rate": 70,
"target_exchange_rate": 1,
"reference_no": "1",
"reference_date": nowdate(),
"received_amount": 70,
"paid_amount": 1,
"paid_from": "_Test Receivable USD - _TC",
"paid_to": "_Test Cash - _TC",
}
)
pe.insert()
pe.submit()
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=75,
do_not_save=1,
rate=1,
)
si = si.save()
si.append(
"advances",
{
"reference_type": "Payment Entry",
"reference_name": pe.name,
"advance_amount": 1,
"allocated_amount": 1,
"ref_exchange_rate": 70,
},
)
si.save()
si.submit()
expected_gle = [
["_Test Receivable USD - _TC", 75.0, 5.0],
["Exchange Gain/Loss - _TC", 5.0, 0.0],
["Sales - _TC", 0.0, 75.0],
]
check_gl_entries(self, si.name, expected_gle, nowdate())
# cancel advance payment
pe.reload()
pe.cancel()
expected_gle_after = [
["_Test Receivable USD - _TC", 75.0, 0.0],
["Sales - _TC", 0.0, 75.0],
]
check_gl_entries(self, si.name, expected_gle_after, nowdate())
frappe.db.set_single_value(
"Accounts Settings", "unlink_payment_on_cancellation_of_invoice", unlink_enabled
)
def test_batch_expiry_for_sales_invoice_return(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.item.test_item import make_item

View File

@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint, getdate
from frappe.utils import cint, flt, getdate
class TaxWithholdingCategory(Document):
@@ -274,7 +274,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
"docstatus": 1,
}
if not tax_details.get("consider_party_ledger_amount") and doctype != "Sales Invoice":
if doctype != "Sales Invoice":
filters.update(
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
)
@@ -518,10 +518,19 @@ def get_invoice_total_without_tcs(inv, tax_details):
def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total):
tds_amount = 0
limit_consumed = frappe.db.get_value(
"Purchase Invoice",
{"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
"sum(net_total)",
limit_consumed = flt(
frappe.db.get_all(
"Purchase Invoice",
filters={
"supplier": ("in", parties),
"apply_tds": 1,
"docstatus": 1,
"tax_withholding_category": ldc.tax_withholding_category,
"posting_date": ("between", (ldc.valid_from, ldc.valid_upto)),
},
fields=["sum(base_net_total) as limit_consumed"],
)[0].get("limit_consumed")
)
if is_valid_certificate(
@@ -535,10 +544,10 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
if current_amount < (certificate_limit - deducted_amount):
if certificate_limit - flt(deducted_amount) - flt(current_amount) >= 0:
return current_amount * rate / 100
else:
ltds_amount = certificate_limit - deducted_amount
ltds_amount = certificate_limit - flt(deducted_amount)
tds_amount = current_amount - ltds_amount
return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100
@@ -549,9 +558,9 @@ def is_valid_certificate(
):
valid = False
if (
getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)
) and certificate_limit > deducted_amount:
available_amount = flt(certificate_limit) - flt(deducted_amount)
if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
valid = True
return valid

View File

@@ -110,9 +110,9 @@ class TestTaxWithholdingCategory(unittest.TestCase):
invoices.append(pi1)
# Cumulative threshold is 30000
# Threshold calculation should be on both the invoices
# TDS should be applied only on 1000
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
# Threshold calculation should be only on the Second invoice
# Second didn't breach, no TDS should be applied
self.assertEqual(pi1.taxes, [])
for d in reversed(invoices):
d.cancel()

View File

@@ -1,20 +1,23 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2016-04-08 14:49:58.133098",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 2,
"is_standard": "Yes",
"modified": "2017-02-24 20:08:26.084484",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Asset Depreciation Ledger",
"owner": "Administrator",
"ref_doctype": "Asset",
"report_name": "Asset Depreciation Ledger",
"report_type": "Script Report",
"add_total_row": 1,
"columns": [],
"creation": "2016-04-08 14:49:58.133098",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 2,
"is_standard": "Yes",
"modified": "2023-06-06 09:00:07.435151",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Asset Depreciation Ledger",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Asset",
"report_name": "Asset Depreciation Ledger",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"

View File

@@ -25,6 +25,7 @@ def get_data(filters):
["posting_date", "<=", filters.get("to_date")],
["against_voucher_type", "=", "Asset"],
["account", "in", depreciation_accounts],
["is_cancelled", "=", 0],
]
if filters.get("asset"):

View File

@@ -1,20 +1,23 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2016-04-08 14:56:37.235981",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 2,
"is_standard": "Yes",
"modified": "2017-02-24 20:08:18.660476",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Asset Depreciations and Balances",
"owner": "Administrator",
"ref_doctype": "Asset",
"report_name": "Asset Depreciations and Balances",
"report_type": "Script Report",
"add_total_row": 1,
"columns": [],
"creation": "2016-04-08 14:56:37.235981",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 2,
"is_standard": "Yes",
"modified": "2023-06-06 11:33:29.611277",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Asset Depreciations and Balances",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Asset",
"report_name": "Asset Depreciations and Balances",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"

View File

@@ -114,28 +114,6 @@ def get_assets(filters):
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.asset_category,
ifnull(sum(case when ds.schedule_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
ds.depreciation_amount
else
0
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s and ds.schedule_date <= a.disposal_date then
ds.depreciation_amount
else
0
end), 0) as depreciation_eliminated_during_the_period,
ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s
and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then
ds.depreciation_amount
else
0
end), 0) as depreciation_amount_during_the_period
from `tabAsset` a, `tabDepreciation Schedule` ds
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent and ifnull(ds.journal_entry, '') != ''
group by a.asset_category
union
SELECT a.asset_category,
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
gle.debit
else
@@ -160,7 +138,7 @@ def get_assets(filters):
aca.parent = a.asset_category and aca.company_name = %(company)s
join `tabCompany` company on
company.name = %(company)s
where a.docstatus=1 and a.company=%(company)s and a.calculate_depreciation=0 and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
group by a.asset_category
union
SELECT a.asset_category,

View File

@@ -79,7 +79,7 @@ def get_entries(filters):
payment_entries = frappe.db.sql(
"""SELECT
"Payment Entry", name, posting_date, reference_no, clearance_date, party,
if(paid_from=%(account)s, paid_amount * -1, received_amount)
if(paid_from=%(account)s, ((paid_amount * -1) - total_taxes_and_charges) , received_amount)
FROM
`tabPayment Entry`
WHERE

View File

@@ -524,11 +524,26 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions.append("cost_center in %(cost_center)s")
if filters.get("include_default_book_entries"):
additional_conditions.append(
"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
)
if filters.get("finance_book"):
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
filters.get("company_fb")
):
frappe.throw(
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
else:
additional_conditions.append(
"(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
)
else:
additional_conditions.append("(finance_book in (%(company_fb)s, '') OR finance_book IS NULL)")
else:
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
if filters.get("finance_book"):
additional_conditions.append(
"(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
)
else:
additional_conditions.append("(finance_book in ('') OR finance_book IS NULL)")
if accounting_dimensions:
for dimension in accounting_dimensions:

View File

@@ -58,9 +58,8 @@ frappe.query_reports["General Ledger"] = {
{
"fieldname":"party_type",
"label": __("Party Type"),
"fieldtype": "Link",
"options": "Party Type",
"default": "",
"fieldtype": "Autocomplete",
options: Object.keys(frappe.boot.party_account_types),
on_change: function() {
frappe.query_report.set_filter_value('party', "");
}
@@ -177,7 +176,8 @@ frappe.query_reports["General Ledger"] = {
{
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
"fieldtype": "Check"
"fieldtype": "Check",
"default": 1
},
{
"fieldname": "show_cancelled_entries",

View File

@@ -287,13 +287,23 @@ def get_conditions(filters):
if filters.get("project"):
conditions.append("project in %(project)s")
if filters.get("finance_book"):
if filters.get("include_default_book_entries"):
conditions.append(
"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
)
if filters.get("include_default_book_entries"):
if filters.get("finance_book"):
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
filters.get("company_fb")
):
frappe.throw(
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
else:
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
else:
conditions.append("finance_book in (%(finance_book)s)")
conditions.append("(finance_book in (%(company_fb)s, '') OR finance_book IS NULL)")
else:
if filters.get("finance_book"):
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
else:
conditions.append("(finance_book in ('') OR finance_book IS NULL)")
if not filters.get("show_cancelled_entries"):
conditions.append("is_cancelled = 0")

View File

@@ -125,12 +125,14 @@ def get_revenue(data, period_list, include_in_gross=1):
data_to_be_removed = True
while data_to_be_removed:
revenue, data_to_be_removed = remove_parent_with_no_child(revenue, period_list)
revenue = adjust_account(revenue, period_list)
revenue, data_to_be_removed = remove_parent_with_no_child(revenue)
adjust_account_totals(revenue, period_list)
return copy.deepcopy(revenue)
def remove_parent_with_no_child(data, period_list):
def remove_parent_with_no_child(data):
data_to_be_removed = False
for parent in data:
if "is_group" in parent and parent.get("is_group") == 1:
@@ -147,16 +149,19 @@ def remove_parent_with_no_child(data, period_list):
return data, data_to_be_removed
def adjust_account(data, period_list, consolidated=False):
leaf_nodes = [item for item in data if item["is_group"] == 0]
def adjust_account_totals(data, period_list):
totals = {}
for node in leaf_nodes:
set_total(node, node["total"], data, totals)
for d in data:
for period in period_list:
key = period if consolidated else period.key
d["total"] = totals[d["account"]]
return data
for d in reversed(data):
if d.get("is_group"):
for period in period_list:
# reset totals for group accounts as totals set by get_data doesn't consider include_in_gross check
d[period.key] = sum(
item[period.key] for item in data if item.get("parent_account") == d.get("account")
)
else:
set_total(d, d["total"], data, totals)
d["total"] = totals[d["account"]]
def set_total(node, value, complete_list, totals):
@@ -191,6 +196,9 @@ def get_profit(
if profit_loss[key]:
has_value = True
if not profit_loss.get("total"):
profit_loss["total"] = 0
profit_loss["total"] += profit_loss[key]
if has_value:
return profit_loss
@@ -229,6 +237,9 @@ def get_net_profit(
if profit_loss[key]:
has_value = True
if not profit_loss.get("total"):
profit_loss["total"] = 0
profit_loss["total"] += profit_loss[key]
if has_value:
return profit_loss

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from collections import OrderedDict
import frappe
from frappe import _, qb, scrub
@@ -8,6 +9,7 @@ from frappe.query_builder import Order
from frappe.utils import cint, flt
from erpnext.controllers.queries import get_match_cond
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
from erpnext.stock.utils import get_incoming_rate
@@ -465,7 +467,14 @@ class GrossProfitGenerator(object):
):
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
for returned_item_row in returned_item_rows:
row.qty += flt(returned_item_row.qty)
# returned_items 'qty' should be stateful
if returned_item_row.qty != 0:
if row.qty >= abs(returned_item_row.qty):
row.qty += returned_item_row.qty
returned_item_row.qty = 0
else:
row.qty = 0
returned_item_row.qty += row.qty
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
if flt(row.qty) or row.base_amount:
@@ -658,12 +667,25 @@ class GrossProfitGenerator(object):
def load_invoice_items(self):
conditions = ""
if self.filters.company:
conditions += " and company = %(company)s"
conditions += " and `tabSales Invoice`.company = %(company)s"
if self.filters.from_date:
conditions += " and posting_date >= %(from_date)s"
if self.filters.to_date:
conditions += " and posting_date <= %(to_date)s"
conditions += " and (is_return = 0 or (is_return=1 and return_against is null))"
if self.filters.item_group:
conditions += " and {0}".format(get_item_group_condition(self.filters.item_group))
if self.filters.sales_person:
conditions += """
and exists(select 1
from `tabSales Team` st
where st.parent = `tabSales Invoice`.name
and st.sales_person = %(sales_person)s)
"""
if self.filters.group_by == "Sales Person":
sales_person_cols = ", sales.sales_person, sales.allocated_amount, sales.incentives"
sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name"
@@ -739,30 +761,30 @@ class GrossProfitGenerator(object):
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
"""
parents = []
grouped = OrderedDict()
for row in self.si_list:
if row.parent not in parents:
parents.append(row.parent)
# initialize list with a header row for each new parent
grouped.setdefault(row.parent, [self.get_invoice_row(row)]).append(
row.update(
{"indent": 1.0, "parent_invoice": row.parent, "invoice_or_item": row.item_code}
) # descendant rows will have indent: 1.0 or greater
)
parents_index = 0
for index, row in enumerate(self.si_list):
if parents_index < len(parents) and row.parent == parents[parents_index]:
invoice = self.get_invoice_row(row)
self.si_list.insert(index, invoice)
parents_index += 1
# if item is a bundle, add it's components as seperate rows
if frappe.db.exists("Product Bundle", row.item_code):
bundled_items = self.get_bundle_items(row)
for x in bundled_items:
bundle_item = self.get_bundle_item_row(row, x)
grouped.get(row.parent).append(bundle_item)
else:
# skipping the bundle items rows
if not row.indent:
row.indent = 1.0
row.parent_invoice = row.parent
row.invoice_or_item = row.item_code
self.si_list.clear()
if frappe.db.exists("Product Bundle", row.item_code):
self.add_bundle_items(row, index)
for items in grouped.values():
self.si_list.extend(items)
def get_invoice_row(self, row):
# header row format
return frappe._dict(
{
"parent_invoice": "",
@@ -791,13 +813,6 @@ class GrossProfitGenerator(object):
}
)
def add_bundle_items(self, product_bundle, index):
bundle_items = self.get_bundle_items(product_bundle)
for i, item in enumerate(bundle_items):
bundle_item = self.get_bundle_item_row(product_bundle, item)
self.si_list.insert((index + i + 1), bundle_item)
def get_bundle_items(self, product_bundle):
return frappe.get_all(
"Product Bundle Item", filters={"parent": product_bundle.item_code}, fields=["item_code", "qty"]

View File

@@ -380,3 +380,82 @@ class TestGrossProfit(FrappeTestCase):
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])
def test_crnote_against_invoice_with_multiple_instances_of_same_item(self):
"""
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)
sinv.append("items", frappe.copy_doc(sinv.items[0], ignore_no_copy=False))
sinv = sinv.save().submit()
# Create Credit Note for Invoice
cr_note = make_sales_return(sinv.name)
cr_note = cr_note.save().submit()
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
columns, data = execute(filters=filters)
expected_entry = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 0.0,
"avg._selling_rate": 0.0,
"valuation_rate": 0.0,
"selling_amount": -100.0,
"buying_amount": 0.0,
"gross_profit": -100.0,
"gross_profit_%": 100.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
# Both items of Invoice should have '0' qty
self.assertEqual(len(gp_entry), 2)
self.assertDictContainsSubset(expected_entry, gp_entry[0])
self.assertDictContainsSubset(expected_entry, gp_entry[1])
def test_standalone_cr_notes(self):
"""
Standalone cr notes will be reported as usual
"""
# Make Cr Note
sinv = self.create_sales_invoice(
qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
)
sinv.is_return = 1
sinv = sinv.save().submit()
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
columns, data = execute(filters=filters)
expected_entry = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": -1.0,
"avg._selling_rate": 100.0,
"valuation_rate": 0.0,
"selling_amount": -100.0,
"buying_amount": 0.0,
"gross_profit": -100.0,
"gross_profit_%": 100.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])

View File

@@ -15,7 +15,6 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i
get_group_by_conditions,
get_tax_accounts,
)
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
def execute(filters=None):
@@ -40,6 +39,16 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
tax_doctype="Purchase Taxes and Charges",
)
scrubbed_tax_fields = {}
for tax in tax_columns:
scrubbed_tax_fields.update(
{
tax + " Rate": frappe.scrub(tax + " Rate"),
tax + " Amount": frappe.scrub(tax + " Amount"),
}
)
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
data = []
@@ -50,11 +59,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get("group_by"):
grand_total = get_grand_total(filters, "Purchase Invoice")
item_details = get_item_details()
for d in item_list:
item_record = item_details.get(d.item_code)
purchase_receipt = None
if d.purchase_receipt:
purchase_receipt = d.purchase_receipt
@@ -67,8 +72,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = {
"item_code": d.item_code,
"item_name": item_record.item_name if item_record else d.item_name,
"item_group": item_record.item_group if item_record else d.item_group,
"item_name": d.pi_item_name if d.pi_item_name else d.i_item_name,
"item_group": d.pi_item_group if d.pi_item_group else d.i_item_group,
"description": d.description,
"invoice": d.parent,
"posting_date": d.posting_date,
@@ -87,7 +92,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
"project": d.project,
"company": d.company,
"purchase_order": d.purchase_order,
"purchase_receipt": d.purchase_receipt,
"purchase_receipt": purchase_receipt,
"expense_account": expense_account,
"stock_qty": d.stock_qty,
"stock_uom": d.stock_uom,
@@ -101,8 +106,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update(
{
frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0),
frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0),
scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0),
}
)
total_tax += flt(item_tax.get("tax_amount"))
@@ -241,7 +246,7 @@ def get_columns(additional_table_columns, filters):
},
{
"label": _("Purchase Receipt"),
"fieldname": "Purchase Receipt",
"fieldname": "purchase_receipt",
"fieldtype": "Link",
"options": "Purchase Receipt",
"width": 100,
@@ -325,15 +330,17 @@ def get_items(filters, additional_query_columns):
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice`.unrealized_profit_loss_account,
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
`tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group,
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
`tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`,
`tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {0}
from `tabPurchase Invoice`, `tabPurchase Invoice Item`
from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem`
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
`tabPurchase Invoice`.docstatus = 1 %s
`tabItem`.name = `tabPurchase Invoice Item`.`item_code` and
`tabPurchase Invoice`.docstatus = 1 %s
""".format(
additional_query_columns
)

View File

@@ -11,7 +11,6 @@ from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import (
get_customer_details,
get_item_details,
)
@@ -35,6 +34,16 @@ def _execute(
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
scrubbed_tax_fields = {}
for tax in tax_columns:
scrubbed_tax_fields.update(
{
tax + " Rate": frappe.scrub(tax + " Rate"),
tax + " Amount": frappe.scrub(tax + " Amount"),
}
)
mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list))
so_dn_map = get_delivery_notes_against_sales_order(item_list)
@@ -47,11 +56,9 @@ def _execute(
grand_total = get_grand_total(filters, "Sales Invoice")
customer_details = get_customer_details()
item_details = get_item_details()
for d in item_list:
customer_record = customer_details.get(d.customer)
item_record = item_details.get(d.item_code)
delivery_note = None
if d.delivery_note:
@@ -64,8 +71,8 @@ def _execute(
row = {
"item_code": d.item_code,
"item_name": item_record.item_name if item_record else d.item_name,
"item_group": item_record.item_group if item_record else d.item_group,
"item_name": d.si_item_name if d.si_item_name else d.i_item_name,
"item_group": d.si_item_group if d.si_item_group else d.i_item_group,
"description": d.description,
"invoice": d.parent,
"posting_date": d.posting_date,
@@ -107,8 +114,8 @@ def _execute(
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update(
{
frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0),
frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0),
scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0),
}
)
if item_tax.get("is_other_charges"):
@@ -399,19 +406,23 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.unrealized_profit_loss_account,
`tabSales Invoice`.is_internal_customer,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.project,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
`tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group,
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
and `tabSales Invoice`.docstatus = 1 {1}
from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and
`tabItem`.name = `tabSales Invoice Item`.`item_code` and
`tabSales Invoice`.docstatus = 1 {1}
""".format(
additional_query_columns or "", conditions
),

View File

@@ -157,12 +157,25 @@ def get_rootwise_opening_balances(filters, report_type):
if filters.project:
additional_conditions += " and project = %(project)s"
company_fb = frappe.db.get_value("Company", filters.company, "default_finance_book")
if filters.get("include_default_book_entries"):
additional_conditions += (
" AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
)
if filters.get("finance_book"):
if company_fb and cstr(filters.get("finance_book")) != cstr(company_fb):
frappe.throw(
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
else:
additional_conditions += (
" AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
)
else:
additional_conditions += " AND (finance_book in (%(company_fb)s, '') OR finance_book IS NULL)"
else:
additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
if filters.get("finance_book"):
additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
else:
additional_conditions += " AND (finance_book in ('') OR finance_book IS NULL)"
accounting_dimensions = get_accounting_dimensions(as_list=False)
@@ -174,7 +187,7 @@ def get_rootwise_opening_balances(filters, report_type):
"year_start_date": filters.year_start_date,
"project": filters.project,
"finance_book": filters.finance_book,
"company_fb": frappe.db.get_value("Company", filters.company, "default_finance_book"),
"company_fb": company_fb,
}
if accounting_dimensions:

View File

@@ -421,6 +421,9 @@ frappe.ui.form.on('Asset', {
} else {
frm.set_value('purchase_date', purchase_doc.posting_date);
}
if (!frm.doc.is_existing_asset && !frm.doc.available_for_use_date) {
frm.set_value('available_for_use_date', frm.doc.purchase_date);
}
const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code);
if (!item) {
doctype_field = frappe.scrub(doctype)

View File

@@ -79,6 +79,9 @@
"options": "ACC-ASS-.YYYY.-"
},
{
"depends_on": "item_code",
"fetch_from": "item_code.item_name",
"fetch_if_empty": 1,
"fieldname": "asset_name",
"fieldtype": "Data",
"in_list_view": 1,
@@ -512,7 +515,7 @@
"table_fieldname": "accounts"
}
],
"modified": "2023-01-31 01:03:09.467817",
"modified": "2023-03-30 15:07:41.542374",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
@@ -554,4 +557,4 @@
"sort_order": "DESC",
"title_field": "asset_name",
"track_changes": 1
}
}

View File

@@ -27,6 +27,7 @@ from erpnext.accounts.general_ledger import make_reverse_gl_entries
from erpnext.assets.doctype.asset.depreciation import (
get_depreciation_accounts,
get_disposal_account_and_cost_center,
is_first_day_of_the_month,
is_last_day_of_the_month,
)
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
@@ -296,17 +297,42 @@ class Asset(AccountsController):
if has_pro_rata:
number_of_pending_depreciations += 1
has_wdv_or_dd_non_yearly_pro_rata = False
if (
finance_book.depreciation_method in ("Written Down Value", "Double Declining Balance")
and cint(finance_book.frequency_of_depreciation) != 12
):
has_wdv_or_dd_non_yearly_pro_rata = self.check_is_pro_rata(
finance_book, wdv_or_dd_non_yearly=True
)
skip_row = False
should_get_last_day = is_last_day_of_the_month(finance_book.depreciation_start_date)
depreciation_amount = 0
for n in range(start[finance_book.idx - 1], number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
if n > 0 and len(self.get("schedules")) > n - 1:
prev_depreciation_amount = self.get("schedules")[n - 1].depreciation_amount
else:
prev_depreciation_amount = 0
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
depreciation_amount = get_depreciation_amount(
self,
value_after_depreciation,
finance_book,
n,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
)
if not has_pro_rata or (
n < (cint(number_of_pending_depreciations) - 1) or number_of_pending_depreciations == 2
):
schedule_date = add_months(
finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation)
)
@@ -314,15 +340,14 @@ class Asset(AccountsController):
if should_get_last_day:
schedule_date = get_last_day(schedule_date)
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_disposal:
from_date = self.get_from_date(finance_book.finance_book)
from_date = self.get_from_date_for_disposal(finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, date_of_disposal
finance_book,
depreciation_amount,
from_date,
date_of_disposal,
)
if depreciation_amount > 0:
@@ -340,17 +365,41 @@ class Asset(AccountsController):
break
# For first row
if has_pro_rata and not self.opening_accumulated_depreciation and n == 0:
if (
n == 0
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
and not self.opening_accumulated_depreciation
):
from_date = add_days(
self.available_for_use_date, -1
) # needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, finance_book.depreciation_start_date
finance_book,
depreciation_amount,
from_date,
finance_book.depreciation_start_date,
has_wdv_or_dd_non_yearly_pro_rata,
)
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
if not is_first_day_of_the_month(getdate(self.available_for_use_date)):
from_date = get_last_day(
add_months(
getdate(self.available_for_use_date),
((self.number_of_depreciations_booked - 1) * finance_book.frequency_of_depreciation),
)
)
else:
from_date = add_months(
getdate(add_days(self.available_for_use_date, -1)),
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book,
depreciation_amount,
from_date,
finance_book.depreciation_start_date,
has_wdv_or_dd_non_yearly_pro_rata,
)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
monthly_schedule_date = add_months(finance_book.depreciation_start_date, -months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
@@ -364,16 +413,18 @@ class Asset(AccountsController):
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, schedule_date, self.to_date
finance_book,
depreciation_amount,
schedule_date,
self.to_date,
has_wdv_or_dd_non_yearly_pro_rata,
)
depreciation_amount = self.get_adjusted_depreciation_amount(
depreciation_amount_without_pro_rata, depreciation_amount, finance_book.finance_book
)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
if not depreciation_amount:
continue
@@ -392,7 +443,7 @@ class Asset(AccountsController):
)
skip_row = True
if depreciation_amount > 0:
if flt(depreciation_amount, self.precision("gross_purchase_amount")) > 0:
self.append(
"schedules",
{
@@ -450,16 +501,19 @@ class Asset(AccountsController):
for idx, s in enumerate(self.schedules, 1):
s.idx = idx
def get_from_date(self, finance_book):
def get_from_date_for_disposal(self, finance_book):
if not self.get("schedules"):
return self.available_for_use_date
return add_months(
getdate(self.available_for_use_date),
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
)
if len(self.finance_books) == 1:
return self.schedules[-1].schedule_date
from_date = ""
for schedule in self.get("schedules"):
if schedule.finance_book == finance_book:
if schedule.finance_book == finance_book.finance_book:
from_date = schedule.schedule_date
if from_date:
@@ -469,28 +523,37 @@ class Asset(AccountsController):
return add_days(self.available_for_use_date, -1)
# if it returns True, depreciation_amount will not be equal for the first and last rows
def check_is_pro_rata(self, row):
def check_is_pro_rata(self, row, wdv_or_dd_non_yearly=False):
has_pro_rata = False
# if not existing asset, from_date = available_for_use_date
# otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
# from_date = 01/01/2022
from_date = self.get_modified_available_for_use_date(row)
from_date = self.get_modified_available_for_use_date(row, wdv_or_dd_non_yearly)
days = date_diff(row.depreciation_start_date, from_date) + 1
# if frequency_of_depreciation is 12 months, total_days = 365
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if wdv_or_dd_non_yearly:
total_days = get_total_days(row.depreciation_start_date, 12)
else:
# if frequency_of_depreciation is 12 months, total_days = 365
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if days < total_days:
has_pro_rata = True
return has_pro_rata
def get_modified_available_for_use_date(self, row):
return add_months(
self.available_for_use_date,
(self.number_of_depreciations_booked * row.frequency_of_depreciation),
)
def get_modified_available_for_use_date(self, row, wdv_or_dd_non_yearly=False):
if wdv_or_dd_non_yearly:
return add_months(
self.available_for_use_date,
(self.number_of_depreciations_booked * 12),
)
else:
return add_months(
self.available_for_use_date,
(self.number_of_depreciations_booked * row.frequency_of_depreciation),
)
def validate_asset_finance_books(self, row):
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
@@ -893,22 +956,51 @@ class Asset(AccountsController):
float_precision = cint(frappe.db.get_default("float_precision")) or 2
if args.get("depreciation_method") == "Double Declining Balance":
return 200.0 / args.get("total_number_of_depreciations")
return 200.0 / (
(
flt(args.get("total_number_of_depreciations"), 2) * flt(args.get("frequency_of_depreciation"))
)
/ 12
)
if args.get("depreciation_method") == "Written Down Value":
if args.get("rate_of_depreciation") and on_validate:
if (
args.get("rate_of_depreciation")
and on_validate
and not self.flags.increase_in_asset_value_due_to_repair
):
return args.get("rate_of_depreciation")
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
if self.flags.increase_in_asset_value_due_to_repair:
value = flt(args.get("expected_value_after_useful_life")) / flt(
args.get("value_after_depreciation")
)
else:
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
depreciation_rate = math.pow(
value,
1.0
/ (
(
flt(args.get("total_number_of_depreciations"), 2)
* flt(args.get("frequency_of_depreciation"))
)
/ 12
),
)
return flt((100 * (1 - depreciation_rate)), float_precision)
def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
def get_pro_rata_amt(
self, row, depreciation_amount, from_date, to_date, has_wdv_or_dd_non_yearly_pro_rata=False
):
days = date_diff(to_date, from_date)
months = month_diff(to_date, from_date)
total_days = get_total_days(to_date, row.frequency_of_depreciation)
if has_wdv_or_dd_non_yearly_pro_rata:
total_days = get_total_days(to_date, 12)
else:
total_days = get_total_days(to_date, row.frequency_of_depreciation)
return (depreciation_amount * flt(days)) / flt(total_days), days, months
@@ -1169,20 +1261,71 @@ def get_total_days(date, frequency):
@erpnext.allow_regional
def get_depreciation_amount(asset, depreciable_value, row):
def get_depreciation_amount(
asset,
depreciable_value,
row,
schedule_idx=0,
prev_depreciation_amount=0,
has_wdv_or_dd_non_yearly_pro_rata=False,
):
if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life:
depreciation_amount = (
flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations)
# if the Depreciation Schedule is being modified after Asset Repair
else:
depreciation_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
return get_straight_line_or_manual_depr_amount(asset, row)
else:
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
return get_wdv_or_dd_depr_amount(
depreciable_value,
row.rate_of_depreciation,
row.frequency_of_depreciation,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
)
return depreciation_amount
def get_straight_line_or_manual_depr_amount(asset, row):
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
if asset.flags.increase_in_asset_life:
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
date_diff(asset.to_date, asset.available_for_use_date) / 365
)
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
elif asset.flags.increase_in_asset_value_due_to_repair:
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
)
# if the Depreciation Schedule is being prepared for the first time
else:
return (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
def get_wdv_or_dd_depr_amount(
depreciable_value,
rate_of_depreciation,
frequency_of_depreciation,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
):
if cint(frequency_of_depreciation) == 12:
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
else:
if has_wdv_or_dd_non_yearly_pro_rata:
if schedule_idx == 0:
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1:
return (
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
)
else:
return prev_depreciation_amount
else:
if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0:
return (
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
)
else:
return prev_depreciation_amount

View File

@@ -33,7 +33,7 @@ frappe.listview_settings['Asset'] = {
}
},
onload: function(me) {
me.page.add_action_item('Make Asset Movement', function() {
me.page.add_action_item(__("Make Asset Movement"), function() {
const assets = me.get_checked_items();
frappe.call({
method: "erpnext.assets.doctype.asset.asset.make_asset_movement",

View File

@@ -8,6 +8,7 @@ from frappe.utils import (
add_months,
cint,
flt,
get_first_day,
get_last_day,
get_link_to_form,
getdate,
@@ -133,16 +134,17 @@ def make_depreciation_entry(asset_name, date=None):
je.append("accounts", debit_entry)
je.flags.ignore_permissions = True
je.flags.planned_depr_entry = True
je.save()
if not je.meta.get_workflow():
je.submit()
d.db_set("journal_entry", je.name)
idx = cint(d.finance_book_id)
finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation -= d.depreciation_amount
finance_books.db_update()
if not je.meta.get_workflow():
je.submit()
idx = cint(d.finance_book_id)
finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation -= d.depreciation_amount
finance_books.db_update()
asset.db_set("depr_entry_posting_status", "Successful")
@@ -225,10 +227,16 @@ def notify_depr_entry_posting_error(failed_asset_names):
asset_links = get_comma_separated_asset_links(failed_asset_names)
message = (
_("Hi,")
+ "<br>"
+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
_("Hello,")
+ "<br><br>"
+ _("The following assets have failed to automatically post depreciation entries: {0}").format(
asset_links
)
+ "."
+ "<br><br>"
+ _(
"Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table."
)
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
@@ -272,7 +280,7 @@ def scrap_asset(asset_name):
je.company = asset.company
je.remark = "Scrap Entry for asset {0}".format(asset_name)
for entry in get_gl_entries_on_asset_disposal(asset):
for entry in get_gl_entries_on_asset_disposal(asset, date):
entry.update({"reference_type": "Asset", "reference_name": asset_name})
je.append("accounts", entry)
@@ -336,6 +344,9 @@ def modify_depreciation_schedule_for_asset_repairs(asset):
def reverse_depreciation_entry_made_after_disposal(asset, date):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
if not asset.calculate_depreciation:
return
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
@@ -396,7 +407,10 @@ def disposal_happens_in_the_future(posting_date_of_disposal):
return False
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None, date=None):
if not date:
date = getdate()
(
fixed_asset_account,
asset,
@@ -413,23 +427,30 @@ def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
"debit_in_account_currency": asset.gross_purchase_amount,
"debit": asset.gross_purchase_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
{
"account": accumulated_depr_account,
"credit_in_account_currency": accumulated_depr_amount,
"credit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
]
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
if profit_amount:
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
get_profit_gl_entries(
profit_amount, gl_entries, disposal_account, depreciation_cost_center, date
)
return gl_entries
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None, date=None):
if not date:
date = getdate()
(
fixed_asset_account,
asset,
@@ -446,18 +467,26 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
"credit_in_account_currency": asset.gross_purchase_amount,
"credit": asset.gross_purchase_amount,
"cost_center": depreciation_cost_center,
},
{
"account": accumulated_depr_account,
"debit_in_account_currency": accumulated_depr_amount,
"debit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
]
if accumulated_depr_amount:
gl_entries.append(
{
"account": accumulated_depr_account,
"debit_in_account_currency": accumulated_depr_amount,
"debit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
)
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
if profit_amount:
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
get_profit_gl_entries(
profit_amount, gl_entries, disposal_account, depreciation_cost_center, date
)
return gl_entries
@@ -484,7 +513,12 @@ def get_asset_details(asset, finance_book=None):
)
def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
def get_profit_gl_entries(
profit_amount, gl_entries, disposal_account, depreciation_cost_center, date=None
):
if not date:
date = getdate()
debit_or_credit = "debit" if profit_amount < 0 else "credit"
gl_entries.append(
{
@@ -492,6 +526,7 @@ def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciat
"cost_center": depreciation_cost_center,
debit_or_credit: abs(profit_amount),
debit_or_credit + "_in_account_currency": abs(profit_amount),
"posting_date": date,
}
)
@@ -516,3 +551,9 @@ def is_last_day_of_the_month(date):
last_day_of_the_month = get_last_day(date)
return getdate(last_day_of_the_month) == getdate(date)
def is_first_day_of_the_month(date):
first_day_of_the_month = get_first_day(date)
return getdate(first_day_of_the_month) == getdate(date)

View File

@@ -298,6 +298,79 @@ class TestAsset(AssetSetup):
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_gle_made_by_asset_sale_for_existing_asset(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-04-01",
purchase_date="2020-04-01",
expected_value_after_useful_life=0,
total_number_of_depreciations=5,
number_of_depreciations_booked=2,
frequency_of_depreciation=12,
depreciation_start_date="2023-03-31",
opening_accumulated_depreciation=24000,
gross_purchase_amount=60000,
submit=1,
)
expected_depr_values = [
["2023-03-31", 12000, 36000],
["2024-03-31", 12000, 48000],
["2025-03-31", 12000, 60000],
]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_depr_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_depr_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_depr_values[i][2], schedule.accumulated_depreciation_amount)
post_depreciation_entries(date="2023-03-31")
si = create_sales_invoice(
item_code="Macbook Pro", asset=asset.name, qty=1, rate=40000, posting_date=getdate("2023-05-23")
)
asset.load_from_db()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1742.47, 37742.47]]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
self.assertTrue(schedule.journal_entry)
expected_gle = (
(
"_Test Accumulated Depreciations - _TC",
37742.47,
0.0,
),
(
"_Test Fixed Asset - _TC",
0.0,
60000.0,
),
(
"_Test Gain/Loss on Asset Disposal - _TC",
0.0,
17742.47,
),
("Debtors - _TC", 40000.0, 0.0),
)
gle = frappe.db.sql(
"""select account, debit, credit from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no = %s
order by account""",
si.name,
)
self.assertSequenceEqual(gle, expected_gle)
def test_asset_with_maintenance_required_status_after_sale(self):
asset = create_asset(
calculate_depreciation=1,
@@ -569,7 +642,7 @@ class TestDepreciationMethods(AssetSetup):
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [["2032-12-31", 30000.0, 77095.89], ["2033-06-06", 12904.11, 90000.0]]
expected_schedules = [["2032-12-31", 42904.11, 90000.0]]
schedules = [
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in asset.get("schedules")
@@ -613,14 +686,14 @@ class TestDepreciationMethods(AssetSetup):
number_of_depreciations_booked=1,
opening_accumulated_depreciation=50000,
expected_value_after_useful_life=10000,
depreciation_start_date="2030-12-31",
depreciation_start_date="2031-12-31",
total_number_of_depreciations=3,
frequency_of_depreciation=12,
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [["2030-12-31", 33333.50, 83333.50], ["2031-12-31", 6666.50, 90000.0]]
expected_schedules = [["2031-12-31", 33333.50, 83333.50], ["2032-12-31", 6666.50, 90000.0]]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -806,12 +879,12 @@ class TestDepreciationMethods(AssetSetup):
)
expected_schedules = [
["2022-02-28", 647.25, 647.25],
["2022-03-31", 1210.71, 1857.96],
["2022-04-30", 1053.99, 2911.95],
["2022-05-31", 917.55, 3829.5],
["2022-06-30", 798.77, 4628.27],
["2022-07-15", 371.73, 5000.0],
["2022-02-28", 310.89, 310.89],
["2022-03-31", 654.45, 965.34],
["2022-04-30", 654.45, 1619.79],
["2022-05-31", 654.45, 2274.24],
["2022-06-30", 654.45, 2928.69],
["2022-07-15", 2071.31, 5000.0],
]
schedules = [
@@ -1421,7 +1494,7 @@ class TestDepreciationBasics(AssetSetup):
)
self.assertEqual(asset.status, "Submitted")
self.assertEqual(asset.get("value_after_depreciation"), 100000)
self.assertEqual(asset.get_value_after_depreciation(), 100000)
jv = make_journal_entry(
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
@@ -1434,12 +1507,68 @@ class TestDepreciationBasics(AssetSetup):
jv.submit()
asset.reload()
self.assertEqual(asset.get("value_after_depreciation"), 99900)
self.assertEqual(asset.get_value_after_depreciation(), 99900)
jv.cancel()
asset.reload()
self.assertEqual(asset.get("value_after_depreciation"), 100000)
self.assertEqual(asset.get_value_after_depreciation(), 100000)
def test_manual_depreciation_for_depreciable_asset(self):
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
purchase_date="2020-01-30",
available_for_use_date="2020-01-30",
expected_value_after_useful_life=10000,
total_number_of_depreciations=10,
frequency_of_depreciation=1,
submit=1,
)
self.assertEqual(asset.status, "Submitted")
self.assertEqual(asset.get_value_after_depreciation(), 100000)
jv = make_journal_entry(
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
)
for d in jv.accounts:
d.reference_type = "Asset"
d.reference_name = asset.name
jv.voucher_type = "Depreciation Entry"
jv.insert()
jv.submit()
asset.reload()
self.assertEqual(asset.get_value_after_depreciation(), 99900)
jv.cancel()
asset.reload()
self.assertEqual(asset.get_value_after_depreciation(), 100000)
def test_manual_depreciation_with_incorrect_jv_voucher_type(self):
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
purchase_date="2020-01-30",
available_for_use_date="2020-01-30",
expected_value_after_useful_life=10000,
total_number_of_depreciations=10,
frequency_of_depreciation=1,
submit=1,
)
jv = make_journal_entry(
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
)
for d in jv.accounts:
d.reference_type = "Asset"
d.reference_name = asset.name
d.account_type = "Depreciation"
jv.voucher_type = "Journal Entry"
self.assertRaises(frappe.ValidationError, jv.insert)
def create_asset_data():

View File

@@ -96,7 +96,6 @@ class AssetCategory(Document):
frappe.throw(msg, title=_("Missing Account"))
@frappe.whitelist()
def get_asset_category_account(
fieldname, item=None, asset=None, account=None, asset_category=None, company=None
):

View File

@@ -82,6 +82,8 @@ def calculate_next_due_date(
next_due_date = add_years(start_date, 1)
if periodicity == "2 Yearly":
next_due_date = add_years(start_date, 2)
if periodicity == "3 Yearly":
next_due_date = add_years(start_date, 3)
if periodicity == "Quarterly":
next_due_date = add_months(start_date, 3)
if end_date and (

View File

@@ -1,664 +1,156 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2017-10-20 07:10:55.903571",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"actions": [],
"creation": "2017-10-20 07:10:55.903571",
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"maintenance_task",
"maintenance_type",
"column_break_2",
"maintenance_status",
"section_break_2",
"start_date",
"periodicity",
"column_break_4",
"end_date",
"certificate_required",
"section_break_9",
"assign_to",
"column_break_10",
"assign_to_name",
"section_break_10",
"next_due_date",
"column_break_14",
"last_completion_date",
"section_break_7",
"description"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "maintenance_task",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Maintenance Task",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "maintenance_task",
"fieldtype": "Data",
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Maintenance Task",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "maintenance_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Maintenance Type",
"length": 0,
"no_copy": 0,
"options": "Preventive Maintenance\nCalibration",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "maintenance_type",
"fieldtype": "Select",
"label": "Maintenance Type",
"options": "Preventive Maintenance\nCalibration"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "maintenance_status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Maintenance Status",
"length": 0,
"no_copy": 0,
"options": "Planned\nOverdue\nCancelled",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "maintenance_status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Maintenance Status",
"options": "Planned\nOverdue\nCancelled",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Today",
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Start Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "Today",
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Start Date",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "periodicity",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Periodicity",
"length": 0,
"no_copy": 0,
"options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "periodicity",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Periodicity",
"options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly\n3 Yearly",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "End Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "end_date",
"fieldtype": "Date",
"label": "End Date"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "certificate_required",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Certificate Required",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 1,
"translatable": 0,
"unique": 0
},
"default": "0",
"fieldname": "certificate_required",
"fieldtype": "Check",
"label": "Certificate Required",
"search_index": 1,
"set_only_once": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_9",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "assign_to",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Assign To",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "assign_to",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Assign To",
"options": "User"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "assign_to.full_name",
"fieldname": "assign_to_name",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Assign to Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "assign_to_name",
"fieldtype": "Read Only",
"label": "Assign to Name"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_10",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "next_due_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Next Due Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "next_due_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Next Due Date"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_14",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_14",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "last_completion_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Last Completion Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "last_completion_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Last Completion Date"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_7",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldname": "description",
"fieldtype": "Text Editor",
"label": "Description"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-06-18 16:12:04.330021",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Maintenance Task",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
],
"istable": 1,
"links": [],
"modified": "2023-03-23 07:03:07.113452",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Maintenance Task",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -70,19 +70,21 @@ frappe.ui.form.on('Asset Movement', {
else if (frm.doc.purpose === 'Issue') {
fieldnames_to_be_altered = {
target_location: { read_only: 1, reqd: 0 },
source_location: { read_only: 1, reqd: 1 },
source_location: { read_only: 1, reqd: 0 },
from_employee: { read_only: 1, reqd: 0 },
to_employee: { read_only: 0, reqd: 1 }
};
}
Object.keys(fieldnames_to_be_altered).forEach(fieldname => {
let property_to_be_altered = fieldnames_to_be_altered[fieldname];
Object.keys(property_to_be_altered).forEach(property => {
let value = property_to_be_altered[property];
frm.set_df_property(fieldname, property, value, cdn, 'assets');
if (fieldnames_to_be_altered) {
Object.keys(fieldnames_to_be_altered).forEach(fieldname => {
let property_to_be_altered = fieldnames_to_be_altered[fieldname];
Object.keys(property_to_be_altered).forEach(property => {
let value = property_to_be_altered[property];
frm.fields_dict['assets'].grid.update_docfield_property(fieldname, property, value);
});
});
});
frm.refresh_field('assets');
frm.refresh_field('assets');
}
}
});

View File

@@ -37,6 +37,7 @@
"reqd": 1
},
{
"default": "Now",
"fieldname": "transaction_date",
"fieldtype": "Datetime",
"in_list_view": 1,
@@ -95,10 +96,11 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-01-22 12:30:55.295670",
"modified": "2023-06-28 16:54:26.571083",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Movement",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
@@ -148,5 +150,6 @@
}
],
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": []
}

View File

@@ -28,26 +28,20 @@ class AssetMovement(Document):
def validate_location(self):
for d in self.assets:
if self.purpose in ["Transfer", "Issue"]:
if not d.source_location:
d.source_location = frappe.db.get_value("Asset", d.asset, "location")
if not d.source_location:
frappe.throw(_("Source Location is required for the Asset {0}").format(d.asset))
current_location = frappe.db.get_value("Asset", d.asset, "location")
if d.source_location:
current_location = frappe.db.get_value("Asset", d.asset, "location")
if current_location != d.source_location:
frappe.throw(
_("Asset {0} does not belongs to the location {1}").format(d.asset, d.source_location)
)
else:
d.source_location = current_location
if self.purpose == "Issue":
if d.target_location:
frappe.throw(
_(
"Issuing cannot be done to a location. \
Please enter employee who has issued Asset {0}"
"Issuing cannot be done to a location. Please enter employee to issue the Asset {0} to"
).format(d.asset),
title="Incorrect Movement Purpose",
)
@@ -110,12 +104,12 @@ class AssetMovement(Document):
)
def on_submit(self):
self.set_latest_location_in_asset()
self.set_latest_location_and_custodian_in_asset()
def on_cancel(self):
self.set_latest_location_in_asset()
self.set_latest_location_and_custodian_in_asset()
def set_latest_location_in_asset(self):
def set_latest_location_and_custodian_in_asset(self):
current_location, current_employee = "", ""
cond = "1=1"

View File

@@ -47,7 +47,7 @@ class TestAssetMovement(unittest.TestCase):
if not frappe.db.exists("Location", "Test Location 2"):
frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert()
movement1 = create_asset_movement(
create_asset_movement(
purpose="Transfer",
company=asset.company,
assets=[
@@ -58,7 +58,7 @@ class TestAssetMovement(unittest.TestCase):
)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
create_asset_movement(
movement1 = create_asset_movement(
purpose="Transfer",
company=asset.company,
assets=[
@@ -70,21 +70,32 @@ class TestAssetMovement(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
movement1.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
employee = make_employee("testassetmovemp@example.com", company="_Test Company")
create_asset_movement(
purpose="Issue",
company=asset.company,
assets=[{"asset": asset.name, "source_location": "Test Location", "to_employee": employee}],
assets=[{"asset": asset.name, "source_location": "Test Location 2", "to_employee": employee}],
reference_doctype="Purchase Receipt",
reference_name=pr.name,
)
# after issuing asset should belong to an employee not at a location
# after issuing, asset should belong to an employee not at a location
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee)
create_asset_movement(
purpose="Receipt",
company=asset.company,
assets=[{"asset": asset.name, "from_employee": employee, "target_location": "Test Location"}],
reference_doctype="Purchase Receipt",
reference_name=pr.name,
)
# after receiving, asset should belong to a location not at an employee
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
def test_last_movement_cancellation(self):
pr = make_purchase_receipt(
item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"

View File

@@ -39,7 +39,11 @@ class AssetRepair(AccountsController):
def before_submit(self):
self.check_repair_status()
self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
self.increase_asset_value()
if self.get("stock_consumption"):
@@ -49,10 +53,7 @@ class AssetRepair(AccountsController):
if self.get("capitalize_repair_cost"):
self.make_gl_entries()
if (
frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
and self.increase_in_asset_life
):
if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
self.modify_depreciation_schedule()
self.asset_doc.flags.ignore_validate_update_after_submit = True
@@ -62,7 +63,11 @@ class AssetRepair(AccountsController):
def before_cancel(self):
self.asset_doc = frappe.get_doc("Asset", self.asset)
self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
self.decrease_asset_value()
if self.get("stock_consumption"):
@@ -72,10 +77,7 @@ class AssetRepair(AccountsController):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(cancel=True)
if (
frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
and self.increase_in_asset_life
):
if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
self.revert_depreciation_schedule_on_cancellation()
self.asset_doc.flags.ignore_validate_update_after_submit = True

View File

@@ -49,7 +49,7 @@ frappe.ui.form.on('Asset Value Adjustment', {
frm.call({
method: "erpnext.assets.doctype.asset.asset.get_asset_value_after_depreciation",
args: {
asset: frm.doc.asset,
asset_name: frm.doc.asset,
finance_book: frm.doc.finance_book
},
callback: function(r) {

View File

@@ -119,7 +119,9 @@ class AssetValueAdjustment(Document):
if d.depreciation_method in ("Straight Line", "Manual"):
end_date = max(s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx)
total_days = date_diff(end_date, self.date)
rate_per_day = flt(d.value_after_depreciation) / flt(total_days)
rate_per_day = flt(d.value_after_depreciation - d.expected_value_after_useful_life) / flt(
total_days
)
from_date = self.date
else:
no_of_depreciations = len(

View File

@@ -19,68 +19,12 @@ frappe.query_reports["Fixed Asset Register"] = {
options: "\nIn Location\nDisposed",
default: 'In Location'
},
{
"fieldname":"filter_based_on",
"label": __("Period Based On"),
"fieldtype": "Select",
"options": ["Fiscal Year", "Date Range"],
"default": ["Fiscal Year"],
"reqd": 1
},
{
"fieldname":"from_date",
"label": __("Start Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
"reqd": 1
},
{
"fieldname":"to_date",
"label": __("End Date"),
"fieldtype": "Date",
"default": frappe.datetime.nowdate(),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
"reqd": 1
},
{
"fieldname":"from_fiscal_year",
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
"reqd": 1
},
{
"fieldname":"to_fiscal_year",
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
"reqd": 1
},
{
"fieldname":"date_based_on",
"label": __("Date Based On"),
"fieldtype": "Select",
"options": ["Purchase Date", "Available For Use Date"],
"default": "Purchase Date",
"reqd": 1
},
{
fieldname:"asset_category",
label: __("Asset Category"),
fieldtype: "Link",
options: "Asset Category"
},
{
fieldname:"finance_book",
label: __("Finance Book"),
fieldtype: "Link",
options: "Finance Book"
},
{
fieldname:"cost_center",
label: __("Cost Center"),
@@ -96,9 +40,66 @@ frappe.query_reports["Fixed Asset Register"] = {
reqd: 1
},
{
fieldname:"is_existing_asset",
label: __("Is Existing Asset"),
fieldname:"only_existing_assets",
label: __("Only existing assets"),
fieldtype: "Check"
},
{
fieldname:"finance_book",
label: __("Finance Book"),
fieldtype: "Link",
options: "Finance Book",
},
{
"fieldname": "include_default_book_assets",
"label": __("Include Default Book Assets"),
"fieldtype": "Check",
"default": 1
},
{
"fieldname":"filter_based_on",
"label": __("Period Based On"),
"fieldtype": "Select",
"options": ["--Select a period--", "Fiscal Year", "Date Range"],
"default": "--Select a period--",
},
{
"fieldname":"from_date",
"label": __("Start Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
},
{
"fieldname":"to_date",
"label": __("End Date"),
"fieldtype": "Date",
"default": frappe.datetime.nowdate(),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
},
{
"fieldname":"from_fiscal_year",
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
},
{
"fieldname":"to_fiscal_year",
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
},
{
"fieldname":"date_based_on",
"label": __("Date Based On"),
"fieldtype": "Select",
"options": ["Purchase Date", "Available For Use Date"],
"default": "Purchase Date",
"depends_on": "eval: doc.filter_based_on == 'Date Range' || doc.filter_based_on == 'Fiscal Year'",
},
]
};

View File

@@ -2,9 +2,11 @@
# For license information, please see license.txt
from itertools import chain
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import cstr, flt, formatdate, getdate
from erpnext.accounts.report.financial_statements import (
@@ -13,7 +15,6 @@ from erpnext.accounts.report.financial_statements import (
validate_fiscal_year,
)
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
def execute(filters=None):
@@ -45,8 +46,8 @@ def get_conditions(filters):
filters.year_end_date = getdate(fiscal_year.year_end_date)
conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
if filters.get("is_existing_asset"):
conditions["is_existing_asset"] = filters.get("is_existing_asset")
if filters.get("only_existing_assets"):
conditions["is_existing_asset"] = filters.get("only_existing_assets")
if filters.get("asset_category"):
conditions["asset_category"] = filters.get("asset_category")
if filters.get("cost_center"):
@@ -64,11 +65,9 @@ def get_conditions(filters):
def get_data(filters):
data = []
conditions = get_conditions(filters)
depreciation_amount_map = get_finance_book_value_map(filters)
pr_supplier_map = get_purchase_receipt_supplier_map()
pi_supplier_map = get_purchase_invoice_supplier_map()
@@ -102,21 +101,31 @@ def get_data(filters):
]
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
assets_linked_to_fb = frappe.db.get_all(
doctype="Asset Finance Book",
filters={"finance_book": filters.finance_book or ("is", "not set")},
pluck="parent",
)
assets_linked_to_fb = get_assets_linked_to_fb(filters)
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
if filters.include_default_book_assets and company_fb:
finance_book = company_fb
elif filters.finance_book:
finance_book = filters.finance_book
else:
finance_book = None
depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book)
for asset in assets_record:
if filters.finance_book:
if asset.asset_id not in assets_linked_to_fb:
continue
else:
if asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb:
continue
if (
assets_linked_to_fb
and asset.calculate_depreciation
and asset.asset_id not in assets_linked_to_fb
):
continue
asset_value = get_asset_value_after_depreciation(
asset.asset_id, finance_book
) or get_asset_value_after_depreciation(asset.asset_id)
asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book)
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
@@ -127,7 +136,7 @@ def get_data(filters):
or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters),
"depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map),
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
@@ -141,14 +150,23 @@ def get_data(filters):
def prepare_chart_data(data, filters):
labels_values_map = {}
date_field = frappe.scrub(filters.date_based_on)
if filters.filter_based_on not in ("Date Range", "Fiscal Year"):
filters_filter_based_on = "Date Range"
date_field = "purchase_date"
filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field)
filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field)
else:
filters_filter_based_on = filters.filter_based_on
date_field = frappe.scrub(filters.date_based_on)
filters_from_date = filters.from_date
filters_to_date = filters.to_date
period_list = get_period_list(
filters.from_fiscal_year,
filters.to_fiscal_year,
filters.from_date,
filters.to_date,
filters.filter_based_on,
filters_from_date,
filters_to_date,
filters_filter_based_on,
"Monthly",
company=filters.company,
ignore_fiscal_year=True,
@@ -172,11 +190,11 @@ def prepare_chart_data(data, filters):
"datasets": [
{
"name": _("Asset Value"),
"values": [d.get("asset_value") for d in labels_values_map.values()],
"values": [flt(d.get("asset_value"), 2) for d in labels_values_map.values()],
},
{
"name": _("Depreciatied Amount"),
"values": [d.get("depreciated_amount") for d in labels_values_map.values()],
"values": [flt(d.get("depreciated_amount"), 2) for d in labels_values_map.values()],
},
],
},
@@ -185,57 +203,76 @@ def prepare_chart_data(data, filters):
}
def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters):
if asset.calculate_depreciation:
depr_amount = depreciation_amount_map.get(asset.asset_id) or 0.0
else:
depr_amount = get_manual_depreciation_amount_of_asset(asset, filters)
def get_assets_linked_to_fb(filters):
afb = frappe.qb.DocType("Asset Finance Book")
return flt(depr_amount, 2)
def get_finance_book_value_map(filters):
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
return frappe._dict(
frappe.db.sql(
""" Select
parent, SUM(depreciation_amount)
FROM `tabDepreciation Schedule`
WHERE
parentfield='schedules'
AND schedule_date<=%s
AND journal_entry IS NOT NULL
AND ifnull(finance_book, '')=%s
GROUP BY parent""",
(date, cstr(filters.finance_book or "")),
)
query = frappe.qb.from_(afb).select(
afb.parent,
)
if filters.include_default_book_assets:
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
def get_manual_depreciation_amount_of_asset(asset, filters):
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 Book Assets'"))
query = query.where(
(afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
| (afb.finance_book.isnull())
)
else:
query = query.where(
(afb.finance_book.isin([cstr(filters.finance_book), ""])) | (afb.finance_book.isnull())
)
assets_linked_to_fb = list(chain(*query.run(as_list=1)))
return assets_linked_to_fb
def get_depreciation_amount_of_asset(asset, depreciation_amount_map):
return depreciation_amount_map.get(asset.asset_id) or 0.0
def get_asset_depreciation_amount_map(filters, finance_book):
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
(_, _, depreciation_expense_account) = get_depreciation_accounts(asset)
asset = frappe.qb.DocType("Asset")
gle = frappe.qb.DocType("GL Entry")
aca = frappe.qb.DocType("Asset Category Account")
company = frappe.qb.DocType("Company")
result = (
query = (
frappe.qb.from_(gle)
.select(Sum(gle.debit))
.where(gle.against_voucher == asset.asset_id)
.where(gle.account == depreciation_expense_account)
.join(asset)
.on(gle.against_voucher == asset.name)
.join(aca)
.on((aca.parent == asset.asset_category) & (aca.company_name == asset.company))
.join(company)
.on(company.name == asset.company)
.select(asset.name.as_("asset"), Sum(gle.debit).as_("depreciation_amount"))
.where(
gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account)
)
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.where(gle.posting_date <= date)
).run()
.where(asset.docstatus == 1)
.groupby(asset.name)
)
if result and result[0] and result[0][0]:
depr_amount = result[0][0]
if finance_book:
query = query.where(
(gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull())
)
else:
depr_amount = 0
query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull()))
return depr_amount
if filters.filter_based_on in ("Date Range", "Fiscal Year"):
query = query.where(gle.posting_date <= date)
asset_depr_amount_map = query.run()
return dict(asset_depr_amount_map)
def get_purchase_receipt_supplier_map():
@@ -310,7 +347,7 @@ def get_columns(filters):
return [
{
"label": _("Asset Id"),
"label": _("Asset ID"),
"fieldtype": "Link",
"fieldname": "asset_id",
"options": "Asset",

View File

@@ -14,6 +14,7 @@
"column_break_3",
"po_required",
"pr_required",
"over_order_allowance",
"maintain_same_rate",
"allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice",
@@ -42,57 +43,6 @@
"label": "Default Buying Price List",
"options": "Price List"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "po_required",
"fieldtype": "Select",
"label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
"options": "No\nYes"
},
{
"fieldname": "pr_required",
"fieldtype": "Select",
"label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
"options": "No\nYes"
},
{
"default": "0",
"fieldname": "maintain_same_rate",
"fieldtype": "Check",
"label": "Maintain Same Rate Throughout the Purchase Cycle"
},
{
"default": "0",
"fieldname": "allow_multiple_items",
"fieldtype": "Check",
"label": "Allow Item To Be Added Multiple Times in a Transaction"
},
{
"fieldname": "subcontract",
"fieldtype": "Section Break",
"label": "Subcontract"
},
{
"default": "Material Transferred for Subcontract",
"fieldname": "backflush_raw_materials_of_subcontract_based_on",
"fieldtype": "Select",
"label": "Backflush Raw Materials of Subcontract Based On",
"options": "BOM\nMaterial Transferred for Subcontract"
},
{
"depends_on": "eval:doc.backflush_raw_materials_of_subcontract_based_on == \"BOM\"",
"description": "Percentage you are allowed to transfer more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to transfer 110 units.",
"fieldname": "over_transfer_allowance",
"fieldtype": "Float",
"label": "Over Transfer Allowance (%)"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"default": "Stop",
"depends_on": "maintain_same_rate",
@@ -110,12 +60,70 @@
"label": "Role Allowed to Override Stop Action",
"options": "Role"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "po_required",
"fieldtype": "Select",
"label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
"options": "No\nYes"
},
{
"fieldname": "pr_required",
"fieldtype": "Select",
"label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
"options": "No\nYes"
},
{
"default": "0",
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
"fieldname": "over_order_allowance",
"fieldtype": "Float",
"label": "Over Order Allowance (%)"
},
{
"default": "0",
"fieldname": "maintain_same_rate",
"fieldtype": "Check",
"label": "Maintain Same Rate Throughout the Purchase Cycle"
},
{
"default": "0",
"fieldname": "allow_multiple_items",
"fieldtype": "Check",
"label": "Allow Item To Be Added Multiple Times in a Transaction"
},
{
"default": "1",
"description": "If checked, Rejected Quantity will be included while making Purchase Invoice from Purchase Receipt.",
"fieldname": "bill_for_rejected_quantity_in_purchase_invoice",
"fieldtype": "Check",
"label": "Bill for Rejected Quantity in Purchase Invoice"
},
{
"fieldname": "subcontract",
"fieldtype": "Section Break",
"label": "Subcontract"
},
{
"default": "Material Transferred for Subcontract",
"fieldname": "backflush_raw_materials_of_subcontract_based_on",
"fieldtype": "Select",
"label": "Backflush Raw Materials of Subcontract Based On",
"options": "BOM\nMaterial Transferred for Subcontract"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.backflush_raw_materials_of_subcontract_based_on == \"BOM\"",
"description": "Percentage you are allowed to transfer more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to transfer 110 units.",
"fieldname": "over_transfer_allowance",
"fieldtype": "Float",
"label": "Over Transfer Allowance (%)"
}
],
"icon": "fa fa-cog",
@@ -123,7 +131,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-09-08 19:26:23.548837",
"modified": "2023-03-22 13:01:49.640869",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

View File

@@ -191,11 +191,15 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
cur_frm.add_custom_button(__('Purchase Invoice'),
this.make_purchase_invoice, __('Create'));
if(flt(doc.per_billed)==0 && doc.status != "Delivered") {
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
if(flt(doc.per_billed) < 100 && doc.status != "Delivered") {
this.frm.add_custom_button(
__('Payment'),
() => this.make_payment_entry(),
__('Create')
);
}
if(flt(doc.per_billed)==0) {
if(flt(doc.per_billed) < 100) {
this.frm.add_custom_button(__('Payment Request'),
function() { me.make_payment_request() }, __('Create'));
}
@@ -238,7 +242,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
source_name: this.frm.doc.supplier,
target: this.frm,
setters: {
company: me.frm.doc.company
company: this.frm.doc.company
},
get_query_filters: {
docstatus: ["!=", 2],

View File

@@ -21,6 +21,9 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category
from erpnext.accounts.party import get_party_account, get_party_account_currency
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
from erpnext.controllers.buying_controller import BuyingController
from erpnext.manufacturing.doctype.blanket_order.blanket_order import (
validate_against_blanket_order,
)
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.doctype.item.item import get_item_defaults, get_last_purchase_details
from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty
@@ -72,6 +75,7 @@ class PurchaseOrder(BuyingController):
self.validate_bom_for_subcontracting_items()
self.create_raw_materials_supplied("supplied_items")
self.set_received_qty_for_drop_ship_items()
validate_against_blanket_order(self)
validate_inter_company_party(
self.doctype, self.supplier, self.company, self.inter_company_order_reference
)
@@ -640,7 +644,7 @@ def make_rm_stock_entry(purchase_order, rm_items):
}
stock_entry.add_to_stock_entry_detail(items_dict)
stock_entry.set_missing_values()
stock_entry.set_missing_values(raise_error_if_no_rate=False)
return stock_entry.as_dict()
else:
frappe.throw(_("No Items selected for transfer"))

View File

@@ -64,7 +64,7 @@ frappe.ui.form.on("Supplier", {
// custom buttons
frm.add_custom_button(__('Accounting Ledger'), function () {
frappe.set_route('query-report', 'General Ledger',
{ party_type: 'Supplier', party: frm.doc.name });
{ party_type: 'Supplier', party: frm.doc.name, party_name: frm.doc.supplier_name });
}, __("View"));
frm.add_custom_button(__('Accounts Payable'), function () {

View File

@@ -128,18 +128,9 @@ class Supplier(TransactionBase):
def on_trash(self):
if self.supplier_primary_contact:
frappe.db.sql(
"""
UPDATE `tabSupplier`
SET
supplier_primary_contact=null,
supplier_primary_address=null,
mobile_no=null,
email_id=null,
primary_address=null
WHERE name=%(name)s""",
{"name": self.name},
)
self.db_set("supplier_primary_contact", None)
if self.supplier_primary_address:
self.db_set("supplier_primary_address", None)
delete_contact_and_address("Supplier", self.name)

View File

@@ -3,6 +3,7 @@
import frappe
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.test_runner import make_test_records
from frappe.tests.utils import FrappeTestCase
@@ -151,6 +152,44 @@ class TestSupplier(FrappeTestCase):
# Rollback
address.delete()
def test_serach_fields_for_supplier(self):
from erpnext.controllers.queries import supplier_query
frappe.db.set_value("Buying Settings", None, "supp_master_name", "Naming Series")
supplier_name = create_supplier(supplier_name="Test Supplier 1").name
make_property_setter(
"Supplier", None, "search_fields", "supplier_group", "Data", for_doctype="Doctype"
)
data = supplier_query(
"Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True
)
self.assertEqual(data[0].name, supplier_name)
self.assertEqual(data[0].supplier_group, "Services")
self.assertTrue("supplier_type" not in data[0])
make_property_setter(
"Supplier",
None,
"search_fields",
"supplier_group, supplier_type",
"Data",
for_doctype="Doctype",
)
data = supplier_query(
"Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True
)
self.assertEqual(data[0].name, supplier_name)
self.assertEqual(data[0].supplier_group, "Services")
self.assertEqual(data[0].supplier_type, "Company")
self.assertTrue("supplier_type" in data[0])
frappe.db.set_value("Buying Settings", None, "supp_master_name", "Supplier Name")
def create_supplier(**args):
args = frappe._dict(args)

View File

@@ -84,7 +84,7 @@ def get_data(conditions, filters):
and po.docstatus = 1
{0}
GROUP BY poi.name
ORDER BY po.transaction_date ASC
ORDER BY po.transaction_date ASC, poi.item_code ASC
""".format(
conditions
),

View File

@@ -5,7 +5,7 @@
import json
import frappe
from frappe import _, throw
from frappe import _, bold, throw
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
from frappe.query_builder.functions import Sum
from frappe.utils import (
@@ -256,8 +256,8 @@ class AccountsController(TransactionBase):
self.validate_payment_schedule_dates()
self.set_due_date()
self.set_payment_schedule()
self.validate_payment_schedule_amount()
if not self.get("ignore_default_payment_terms_template"):
self.validate_payment_schedule_amount()
self.validate_due_date()
self.validate_advance_entries()
@@ -388,6 +388,15 @@ class AccountsController(TransactionBase):
msg += _("Please create purchase from internal sale or delivery document itself")
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
label = "Delivery Note Item" if self.doctype == "Purchase Receipt" else "Sales Invoice Item"
field = frappe.scrub(label)
for row in self.get("items"):
if not row.get(field):
msg = f"At Row {row.idx}: The field {bold(label)} is mandatory for internal transfer"
frappe.throw(_(msg), title=_("Internal Transfer Reference Missing"))
def disable_pricing_rule_on_internal_transfer(self):
if not self.get("ignore_pricing_rule") and self.is_internal_transfer():
self.ignore_pricing_rule = 1
@@ -882,6 +891,9 @@ class AccountsController(TransactionBase):
return is_inclusive
def should_show_taxes_as_table_in_print(self):
return cint(frappe.db.get_single_value("Accounts Settings", "show_taxes_as_table_in_print"))
def validate_advance_entries(self):
order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
@@ -1577,6 +1589,7 @@ class AccountsController(TransactionBase):
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
grand_total = self.get("rounded_total") or self.grand_total
automatically_fetch_payment_terms = 0
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
@@ -1622,19 +1635,23 @@ class AccountsController(TransactionBase):
)
self.append("payment_schedule", data)
for d in self.get("payment_schedule"):
if d.invoice_portion:
d.payment_amount = flt(
grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount")
)
d.base_payment_amount = flt(
base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount")
)
d.outstanding = d.payment_amount
elif not d.invoice_portion:
d.base_payment_amount = flt(
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
)
if not (
automatically_fetch_payment_terms
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
):
for d in self.get("payment_schedule"):
if d.invoice_portion:
d.payment_amount = flt(
grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount")
)
d.base_payment_amount = flt(
base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount")
)
d.outstanding = d.payment_amount
elif not d.invoice_portion:
d.base_payment_amount = flt(
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
)
def get_order_details(self):
if self.doctype == "Sales Invoice":
@@ -1687,6 +1704,10 @@ class AccountsController(TransactionBase):
"invoice_portion": schedule.invoice_portion,
"mode_of_payment": schedule.mode_of_payment,
"description": schedule.description,
"payment_amount": schedule.payment_amount,
"base_payment_amount": schedule.base_payment_amount,
"outstanding": schedule.outstanding,
"paid_amount": schedule.paid_amount,
}
if schedule.discount_type == "Percentage":

View File

@@ -30,10 +30,16 @@ def set_print_templates_for_taxes(doc, settings):
doc.print_templates.update(
{
"total": "templates/print_formats/includes/total.html",
"taxes": "templates/print_formats/includes/taxes.html",
}
)
if not doc.should_show_taxes_as_table_in_print():
doc.print_templates.update(
{
"taxes": "templates/print_formats/includes/taxes.html",
}
)
def format_columns(display_columns, compact_fields):
compact_fields = compact_fields + ["image", "item_code", "item_name"]

View File

@@ -78,18 +78,16 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def customer_query(doctype, txt, searchfield, start, page_len, filters):
def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
doctype = "Customer"
conditions = []
cust_master_name = frappe.defaults.get_user_default("cust_master_name")
if cust_master_name == "Customer Name":
fields = ["name", "customer_group", "territory"]
else:
fields = ["name", "customer_name", "customer_group", "territory"]
fields = ["name"]
if cust_master_name != "Customer Name":
fields.append("customer_name")
fields = get_fields(doctype, fields)
searchfields = frappe.get_meta(doctype).get_search_fields()
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
@@ -112,20 +110,20 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
}
),
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
as_dict=as_dict,
)
# searches for supplier
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
doctype = "Supplier"
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
if supp_master_name == "Supplier Name":
fields = ["name", "supplier_group"]
else:
fields = ["name", "supplier_name", "supplier_group"]
fields = ["name"]
if supp_master_name != "Supplier Name":
fields.append("supplier_name")
fields = get_fields(doctype, fields)
@@ -145,6 +143,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
**{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
),
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
as_dict=as_dict,
)

View File

@@ -450,7 +450,7 @@ class StatusUpdater(Document):
ifnull((select
ifnull(sum(if(abs(%(target_ref_field)s) > abs(%(target_field)s), abs(%(target_field)s), abs(%(target_ref_field)s))), 0)
/ sum(abs(%(target_ref_field)s)) * 100
from `tab%(target_dt)s` where parent="%(name)s" having sum(abs(%(target_ref_field)s)) > 0), 0), 6)
from `tab%(target_dt)s` where parent='%(name)s' and parenttype='%(target_parent_dt)s' having sum(abs(%(target_ref_field)s)) > 0), 0), 6)
%(update_modified)s
where name='%(name)s'"""
% args

View File

@@ -678,7 +678,7 @@ class StockController(AccountsController):
message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
return message
def repost_future_sle_and_gle(self):
def repost_future_sle_and_gle(self, force=False):
args = frappe._dict(
{
"posting_date": self.posting_date,
@@ -689,7 +689,10 @@ class StockController(AccountsController):
}
)
if future_sle_exists(args) or repost_required_for_queue(self):
if self.docstatus == 2:
force = True
if force or future_sle_exists(args) or repost_required_for_queue(self):
item_based_reposting = cint(
frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")
)

View File

@@ -98,7 +98,7 @@ def get_data(filters):
`tabAddress`.name=`tabDynamic Link`.parent)
WHERE
company = %(company)s
AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s
AND DATE(`tabLead`.creation) BETWEEN %(from_date)s AND %(to_date)s
{conditions}
ORDER BY
`tabLead`.creation asc """.format(

View File

@@ -90,7 +90,7 @@ def get_data(filters):
{join}
WHERE
`tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s
AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s
AND DATE(`tabOpportunity`.modified) BETWEEN %(from_date)s AND %(to_date)s
{conditions}
GROUP BY
`tabOpportunity`.name

View File

@@ -198,8 +198,14 @@ class TestWebsiteItem(unittest.TestCase):
breadcrumbs = get_parent_item_groups(item.item_group)
settings = frappe.get_cached_doc("E Commerce Settings")
if settings.enable_field_filters:
base_breadcrumb = "Shop by Category"
else:
base_breadcrumb = "All Products"
self.assertEqual(breadcrumbs[0]["name"], "Home")
self.assertEqual(breadcrumbs[1]["name"], "All Products")
self.assertEqual(breadcrumbs[1]["name"], base_breadcrumb)
self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")

View File

@@ -78,9 +78,10 @@ erpnext.ProductList = class {
let title_html = `<div style="display: flex; margin-left: -15px;">`;
title_html += `
<div class="col-8" style="margin-right: -15px;">
<a class="" href="/${ item.route || '#' }"
style="color: var(--gray-800); font-weight: 500;">
<a href="/${ item.route || '#' }">
<div class="product-title">
${ title }
</div>
</a>
</div>
`;
@@ -201,4 +202,4 @@ erpnext.ProductList = class {
}
}
};
};

View File

@@ -165,6 +165,7 @@
"fieldname": "slide_3_content_align",
"fieldtype": "Select",
"label": "Content Align",
"options": "Left\nCentre\nRight",
"reqd": 0
},
{
@@ -214,6 +215,7 @@
"fieldname": "slide_4_content_align",
"fieldtype": "Select",
"label": "Content Align",
"options": "Left\nCentre\nRight",
"reqd": 0
},
{
@@ -263,6 +265,7 @@
"fieldname": "slide_5_content_align",
"fieldtype": "Select",
"label": "Content Align",
"options": "Left\nCentre\nRight",
"reqd": 0
},
{
@@ -274,7 +277,7 @@
}
],
"idx": 2,
"modified": "2021-02-24 15:57:05.889709",
"modified": "2023-05-12 15:03:57.604060",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "Hero Slider",

View File

@@ -29,6 +29,10 @@ doctype_js = {
override_doctype_class = {"Address": "erpnext.accounts.custom.address.ERPNextAddress"}
override_whitelisted_methods = {
"frappe.www.contact.send_message": "erpnext.templates.utils.send_message"
}
welcome_email = "erpnext.setup.utils.welcome_email"
# setup wizard

View File

@@ -4,6 +4,7 @@
import unittest
import frappe
from frappe.tests.utils import change_settings
from frappe.utils import add_days, getdate
from erpnext.hr.doctype.employee.test_employee import make_employee
@@ -99,6 +100,16 @@ class TestEmployeeTransfer(unittest.TestCase):
self.assertEqual(data.from_date, dt[0])
self.assertEqual(data.to_date, None)
@change_settings("System Settings", {"number_format": "#.###,##"})
def test_data_formatting_in_history(self):
from erpnext.hr.utils import get_formatted_value
value = get_formatted_value("12.500,00", "Float")
self.assertEqual(value, 12500.0)
value = get_formatted_value("12.500,00", "Currency")
self.assertEqual(value, 12500.0)
def create_company():
if not frappe.db.exists("Company", "Test Company"):

View File

@@ -333,7 +333,6 @@ def get_leave_allocation_for_period(
).run()[0][0] or 0.0
@frappe.whitelist()
def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None):
"""Returns carry forwarded leaves for the given employee"""
unused_leaves = 0.0

View File

@@ -873,6 +873,9 @@ def get_leave_allocation_records(employee, date, leave_type=None):
| (
(Ledger.is_carry_forward == 1)
& (Ledger.to_date.between(LeaveAllocation.from_date, LeaveAllocation.to_date))
# only consider cf leaves from current allocation
& (LeaveAllocation.from_date <= date)
& (date <= LeaveAllocation.to_date)
)
)
)

View File

@@ -1170,25 +1170,51 @@ class TestLeaveApplication(unittest.TestCase):
details = get_leave_allocation_records(employee.name, add_days(cf_expiry, 1), leave_type.name)
self.assertEqual(details.get(leave_type.name), expected_data)
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_filtered_old_cf_entries_in_get_leave_allocation_records(self):
"""Tests whether old cf entries are ignored while fetching current allocation records"""
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90,
)
# old allocation with cf leaves
create_carry_forwarded_allocation(employee, leave_type, date="2019-01-01")
# new allocation with cf leaves
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
cf_expiry = frappe.db.get_value(
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
)
# test total leaves allocated before cf leave expiry
details = get_leave_allocation_records(employee.name, add_days(cf_expiry, -1), leave_type.name)
# filters out old CF leaves (15 i.e total 45)
self.assertEqual(details[leave_type.name]["total_leaves_allocated"], 30.0)
def create_carry_forwarded_allocation(employee, leave_type, date=None):
date = date or nowdate()
def create_carry_forwarded_allocation(employee, leave_type):
# initial leave allocation
leave_allocation = create_leave_allocation(
leave_type="_Test_CF_leave_expiry",
employee=employee.name,
employee_name=employee.employee_name,
from_date=add_months(nowdate(), -24),
to_date=add_months(nowdate(), -12),
from_date=add_months(date, -24),
to_date=add_months(date, -12),
carry_forward=0,
)
leave_allocation.submit()
# carry forward leave allocation
leave_allocation = create_leave_allocation(
leave_type="_Test_CF_leave_expiry",
employee=employee.name,
employee_name=employee.employee_name,
from_date=add_days(nowdate(), -84),
to_date=add_days(nowdate(), 100),
from_date=add_days(date, -84),
to_date=add_days(date, 100),
carry_forward=1,
)
leave_allocation.submit()

View File

@@ -8,7 +8,15 @@ from math import ceil
import frappe
from frappe import _, bold
from frappe.model.document import Document
from frappe.utils import date_diff, flt, formatdate, get_last_day, get_link_to_form, getdate
from frappe.utils import (
comma_and,
date_diff,
flt,
formatdate,
get_last_day,
get_link_to_form,
getdate,
)
from six import string_types
@@ -66,7 +74,6 @@ class LeavePolicyAssignment(Document):
).format(frappe.bold(get_link_to_form("Leave Type", leave_type.name)))
frappe.msgprint(msg, indicator="orange", alert=True)
@frappe.whitelist()
def grant_leave_alloc_for_employee(self):
if self.leaves_allocated:
frappe.throw(_("Leave already have been assigned for this Leave Policy Assignment"))
@@ -192,9 +199,9 @@ def add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj
date = getdate(frappe.flags.current_date) or getdate()
if based_on_doj:
# if leave type allocation is based on DOJ, and the date of assignment creation is same as DOJ,
# if leave type allocation is based on DOJ, and the date of assignment creation is after DOJ,
# then the month should be considered
if date.day == date_of_joining.day:
if date.day >= date_of_joining.day:
months_passed += 1
else:
last_day_of_month = get_last_day(date)
@@ -207,7 +214,6 @@ def add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj
@frappe.whitelist()
def create_assignment_for_multiple_employees(employees, data):
if isinstance(employees, string_types):
employees = json.loads(employees)
@@ -215,6 +221,8 @@ def create_assignment_for_multiple_employees(employees, data):
data = frappe._dict(json.loads(data))
docs_name = []
failed = []
for employee in employees:
assignment = frappe.new_doc("Leave Policy Assignment")
assignment.employee = employee
@@ -225,18 +233,45 @@ def create_assignment_for_multiple_employees(employees, data):
assignment.leave_period = data.leave_period or None
assignment.carry_forward = data.carry_forward
assignment.save()
try:
assignment.submit()
except frappe.exceptions.ValidationError:
continue
frappe.db.commit()
savepoint = "before_assignment_submission"
try:
frappe.db.savepoint(savepoint)
assignment.submit()
except Exception as e:
frappe.db.rollback(save_point=savepoint)
frappe.log_error(title=f"Leave Policy Assignment submission failed for {assignment.name}")
failed.append(assignment.name)
docs_name.append(assignment.name)
if failed:
show_assignment_submission_status(failed)
return docs_name
def show_assignment_submission_status(failed):
frappe.clear_messages()
assignment_list = [get_link_to_form("Leave Policy Assignment", entry) for entry in failed]
msg = _("Failed to submit some leave policy assignments:")
msg += " " + comma_and(assignment_list, False) + "<hr>"
msg += (
_("Check {0} for more details")
.format("<a href='/app/List/Error Log?reference_doctype=Leave Policy Assignment'>{0}</a>")
.format(_("Error Log"))
)
frappe.msgprint(
msg,
indicator="red",
title=_("Submission Failed"),
is_minimizable=True,
)
def get_leave_type_details():
leave_type_details = frappe._dict()
leave_types = frappe.get_all(

View File

@@ -52,7 +52,7 @@ frappe.listview_settings['Leave Policy Assignment'] = {
get_query() {
let filters = {"is_active": 1};
if (cur_dialog.fields_dict.company.value)
filters["company"] = cur_dialog.fields_dict.company.value;
filters["company"] = cur_dialog?.fields_dict?.company?.value;
return {
filters: filters

View File

@@ -1,4 +1,5 @@
import frappe
from frappe.query_builder.functions import Count
@frappe.whitelist()
@@ -15,31 +16,34 @@ def get_children(parent=None, company=None, exclude_node=None):
if exclude_node:
filters.append(["name", "!=", exclude_node])
employees = frappe.get_list(
employees = frappe.get_all(
"Employee",
fields=["employee_name as name", "name as id", "reports_to", "image", "designation as title"],
fields=[
"employee_name as name",
"name as id",
"lft",
"rgt",
"reports_to",
"image",
"designation as title",
],
filters=filters,
order_by="name",
)
for employee in employees:
is_expandable = frappe.db.count("Employee", filters={"reports_to": employee.get("id")})
employee.connections = get_connections(employee.id)
employee.expandable = 1 if is_expandable else 0
employee.connections = get_connections(employee.id, employee.lft, employee.rgt)
employee.expandable = bool(employee.connections)
return employees
def get_connections(employee):
num_connections = 0
def get_connections(employee: str, lft: int, rgt: int) -> int:
Employee = frappe.qb.DocType("Employee")
query = (
frappe.qb.from_(Employee)
.select(Count(Employee.name))
.where((Employee.lft > lft) & (Employee.rgt < rgt))
).run()
nodes_to_expand = frappe.get_list("Employee", filters=[["reports_to", "=", employee]])
num_connections += len(nodes_to_expand)
while nodes_to_expand:
parent = nodes_to_expand.pop(0)
descendants = frappe.get_list("Employee", filters=[["reports_to", "=", parent.name]])
num_connections += len(descendants)
nodes_to_expand.extend(descendants)
return num_connections
return query[0][0]

View File

@@ -0,0 +1,52 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.page.organizational_chart.organizational_chart import get_children
class TestOrganizationalChart(FrappeTestCase):
def setUp(self):
self.company = create_company("Test Org Chart").name
frappe.db.delete("Employee", {"company": self.company})
def test_get_children(self):
company = create_company("Test Org Chart").name
emp1 = make_employee("testemp1@mail.com", company=self.company)
emp2 = make_employee("testemp2@mail.com", company=self.company, reports_to=emp1)
emp3 = make_employee("testemp3@mail.com", company=self.company, reports_to=emp1)
make_employee("testemp4@mail.com", company=self.company, reports_to=emp2)
# root node
children = get_children(company=self.company)
self.assertEqual(len(children), 1)
self.assertEqual(children[0].id, emp1)
self.assertEqual(children[0].connections, 3)
# root's children
children = get_children(parent=emp1, company=self.company)
self.assertEqual(len(children), 2)
self.assertEqual(children[0].id, emp2)
self.assertEqual(children[0].connections, 1)
self.assertEqual(children[1].id, emp3)
self.assertEqual(children[1].connections, 0)
def create_company(name):
if frappe.db.exists("Company", name):
return frappe.get_doc("Company", name)
company = frappe.new_doc("Company")
company.update(
{
"company_name": name,
"default_currency": "USD",
"country": "United States",
}
)
return company.insert()

View File

@@ -13,6 +13,7 @@ from frappe.utils import (
formatdate,
get_datetime,
get_link_to_form,
get_number_format_info,
getdate,
nowdate,
today,
@@ -185,15 +186,11 @@ def update_employee_work_history(employee, details, date=None, cancel=False):
field = frappe.get_meta("Employee").get_field(item.fieldname)
if not field:
continue
fieldtype = field.fieldtype
new_data = item.new if not cancel else item.current
if fieldtype == "Date" and new_data:
new_data = getdate(new_data)
elif fieldtype == "Datetime" and new_data:
new_data = get_datetime(new_data)
elif fieldtype in ["Currency", "Float"] and new_data:
new_data = flt(new_data)
setattr(employee, item.fieldname, new_data)
new_value = item.new if not cancel else item.current
new_value = get_formatted_value(new_value, field.fieldtype)
setattr(employee, item.fieldname, new_value)
if item.fieldname in ["department", "designation", "branch"]:
internal_work_history[item.fieldname] = item.new
@@ -207,6 +204,34 @@ def update_employee_work_history(employee, details, date=None, cancel=False):
return employee
def get_formatted_value(value, fieldtype):
"""
Since the fields in Internal Work History table are `Data` fields
format them as per relevant field types
"""
if not value:
return
if fieldtype == "Date":
value = getdate(value)
elif fieldtype == "Datetime":
value = get_datetime(value)
elif fieldtype in ["Currency", "Float"]:
# in case of currency/float, the value might be in user's prefered number format
# instead of machine readable format. Convert it into a machine readable format
number_format = frappe.db.get_default("number_format") or "#,###.##"
decimal_str, comma_str, _number_format_precision = get_number_format_info(number_format)
if comma_str == "." and decimal_str == ",":
value = value.replace(",", "#$")
value = value.replace(".", ",")
value = value.replace("#$", ".")
value = flt(value)
return value
def delete_employee_work_history(details, employee, date):
filters = {}
for d in details:

View File

@@ -160,4 +160,3 @@ class TestLoanDisbursement(unittest.TestCase):
interest = per_day_interest * 15
self.assertEqual(amounts["pending_principal_amount"], 1500000)
self.assertEqual(amounts["interest_amount"], flt(interest + previous_interest, 2))

View File

@@ -22,7 +22,7 @@ class LoanInterestAccrual(AccountsController):
frappe.throw(_("Interest Amount or Principal Amount is mandatory"))
if not self.last_accrual_date:
self.last_accrual_date = get_last_accrual_date(self.loan)
self.last_accrual_date = get_last_accrual_date(self.loan, self.posting_date)
def on_submit(self):
self.make_gl_entries()
@@ -274,14 +274,14 @@ def make_loan_interest_accrual_entry(args):
def get_no_of_days_for_interest_accural(loan, posting_date):
last_interest_accrual_date = get_last_accrual_date(loan.name)
last_interest_accrual_date = get_last_accrual_date(loan.name, posting_date)
no_of_days = date_diff(posting_date or nowdate(), last_interest_accrual_date) + 1
return no_of_days
def get_last_accrual_date(loan):
def get_last_accrual_date(loan, posting_date):
last_posting_date = frappe.db.sql(
""" SELECT MAX(posting_date) from `tabLoan Interest Accrual`
WHERE loan = %s and docstatus = 1""",
@@ -289,12 +289,30 @@ def get_last_accrual_date(loan):
)
if last_posting_date[0][0]:
last_interest_accrual_date = last_posting_date[0][0]
# interest for last interest accrual date is already booked, so add 1 day
return add_days(last_posting_date[0][0], 1)
last_disbursement_date = get_last_disbursement_date(loan, posting_date)
if last_disbursement_date and getdate(last_disbursement_date) > add_days(
getdate(last_interest_accrual_date), 1
):
last_interest_accrual_date = last_disbursement_date
return add_days(last_interest_accrual_date, 1)
else:
return frappe.db.get_value("Loan", loan, "disbursement_date")
def get_last_disbursement_date(loan, posting_date):
last_disbursement_date = frappe.db.get_value(
"Loan Disbursement",
{"docstatus": 1, "against_loan": loan, "posting_date": ("<", posting_date)},
"MAX(posting_date)",
)
return last_disbursement_date
def days_in_year(year):
days = 365

View File

@@ -102,7 +102,7 @@ class LoanRepayment(AccountsController):
if flt(self.total_interest_paid, precision) > flt(self.interest_payable, precision):
if not self.is_term_loan:
# get last loan interest accrual date
last_accrual_date = get_last_accrual_date(self.against_loan)
last_accrual_date = get_last_accrual_date(self.against_loan, self.posting_date)
# get posting date upto which interest has to be accrued
per_day_interest = get_per_day_interest(
@@ -724,7 +724,7 @@ def get_amounts(amounts, against_loan, posting_date):
if due_date:
pending_days = date_diff(posting_date, due_date) + 1
else:
last_accrual_date = get_last_accrual_date(against_loan_doc.name)
last_accrual_date = get_last_accrual_date(against_loan_doc.name, posting_date)
pending_days = date_diff(posting_date, last_accrual_date) + 1
if pending_days > 0:

View File

@@ -7,6 +7,12 @@ frappe.ui.form.on('Blanket Order', {
},
setup: function(frm) {
frm.custom_make_buttons = {
'Purchase Order': 'Purchase Order',
'Sales Order': 'Sales Order',
'Quotation': 'Quotation',
};
frm.add_fetch("customer", "customer_name", "customer_name");
frm.add_fetch("supplier", "supplier_name", "supplier_name");
},

View File

@@ -6,6 +6,7 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder.functions import Sum
from frappe.utils import flt, getdate
from erpnext.stock.doctype.item.item import get_item_defaults
@@ -29,21 +30,23 @@ class BlanketOrder(Document):
def update_ordered_qty(self):
ref_doctype = "Sales Order" if self.blanket_order_type == "Selling" else "Purchase Order"
trans = frappe.qb.DocType(ref_doctype)
trans_item = frappe.qb.DocType(f"{ref_doctype} Item")
item_ordered_qty = frappe._dict(
frappe.db.sql(
"""
select trans_item.item_code, sum(trans_item.stock_qty) as qty
from `tab{0} Item` trans_item, `tab{0}` trans
where trans.name = trans_item.parent
and trans_item.blanket_order=%s
and trans.docstatus=1
and trans.status not in ('Closed', 'Stopped')
group by trans_item.item_code
""".format(
ref_doctype
),
self.name,
)
(
frappe.qb.from_(trans_item)
.from_(trans)
.select(trans_item.item_code, Sum(trans_item.stock_qty).as_("qty"))
.where(
(trans.name == trans_item.parent)
& (trans_item.blanket_order == self.name)
& (trans.docstatus == 1)
& (trans.status.notin(["Stopped", "Closed"]))
)
.groupby(trans_item.item_code)
).run()
)
for d in self.items:
@@ -79,7 +82,43 @@ def make_order(source_name):
"doctype": doctype + " Item",
"field_map": {"rate": "blanket_order_rate", "parent": "blanket_order"},
"postprocess": update_item,
"condition": lambda item: (flt(item.qty) - flt(item.ordered_qty)) > 0,
},
},
)
return target_doc
def validate_against_blanket_order(order_doc):
if order_doc.doctype in ("Sales Order", "Purchase Order"):
order_data = {}
for item in order_doc.get("items"):
if item.against_blanket_order and item.blanket_order:
if item.blanket_order in order_data:
if item.item_code in order_data[item.blanket_order]:
order_data[item.blanket_order][item.item_code] += item.qty
else:
order_data[item.blanket_order][item.item_code] = item.qty
else:
order_data[item.blanket_order] = {item.item_code: item.qty}
if order_data:
allowance = flt(
frappe.db.get_single_value(
"Selling Settings" if order_doc.doctype == "Sales Order" else "Buying Settings",
"over_order_allowance",
)
)
for bo_name, item_data in order_data.items():
bo_doc = frappe.get_doc("Blanket Order", bo_name)
for item in bo_doc.get("items"):
if item.item_code in item_data:
remaining_qty = item.qty - item.ordered_qty
allowed_qty = remaining_qty + (remaining_qty * (allowance / 100))
if allowed_qty < item_data[item.item_code]:
frappe.throw(
_("Item {0} cannot be ordered more than {1} against Blanket Order {2}.").format(
item.item_code, allowed_qty, bo_name
)
)

View File

@@ -63,6 +63,33 @@ class TestBlanketOrder(FrappeTestCase):
po1.currency = get_company_currency(po1.company)
self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
def test_over_order_allowance(self):
# Sales Order
bo = make_blanket_order(blanket_order_type="Selling", quantity=100)
frappe.flags.args.doctype = "Sales Order"
so = make_order(bo.name)
so.currency = get_company_currency(so.company)
so.delivery_date = today()
so.items[0].qty = 110
self.assertRaises(frappe.ValidationError, so.submit)
frappe.db.set_single_value("Selling Settings", "over_order_allowance", 10)
so.submit()
# Purchase Order
bo = make_blanket_order(blanket_order_type="Purchasing", quantity=100)
frappe.flags.args.doctype = "Purchase Order"
po = make_order(bo.name)
po.currency = get_company_currency(po.company)
po.schedule_date = today()
po.items[0].qty = 110
self.assertRaises(frappe.ValidationError, po.submit)
frappe.db.set_single_value("Buying Settings", "over_order_allowance", 10)
po.submit()
def make_blanket_order(**args):
args = frappe._dict(args)

View File

@@ -48,7 +48,8 @@ frappe.ui.form.on("BOM", {
return {
query: "erpnext.manufacturing.doctype.bom.bom.item_query",
filters: {
"item_code": doc.item
"include_item_in_manufacturing": 1,
"is_fixed_asset": 0
}
};
});

View File

@@ -949,7 +949,8 @@ def get_valuation_rate(data):
2) If no value, get last valuation rate from SLE
3) If no value, get valuation rate from Item
"""
from frappe.query_builder.functions import Sum
from frappe.query_builder.functions import Count, IfNull, Sum
from pypika import Case
item_code, company = data.get("item_code"), data.get("company")
valuation_rate = 0.0
@@ -960,7 +961,14 @@ def get_valuation_rate(data):
frappe.qb.from_(bin_table)
.join(wh_table)
.on(bin_table.warehouse == wh_table.name)
.select((Sum(bin_table.stock_value) / Sum(bin_table.actual_qty)).as_("valuation_rate"))
.select(
Case()
.when(
Count(bin_table.name) > 0, IfNull(Sum(bin_table.stock_value) / Sum(bin_table.actual_qty), 0.0)
)
.else_(None)
.as_("valuation_rate")
)
.where((bin_table.item_code == item_code) & (wh_table.company == company))
).run(as_dict=True)[0]
@@ -1338,8 +1346,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
if not has_variants:
query_filters["has_variants"] = 0
if filters and filters.get("is_stock_item"):
query_filters["is_stock_item"] = 1
if filters:
for fieldname, value in filters.items():
query_filters[fieldname] = value
return frappe.get_list(
"Item",

View File

@@ -605,6 +605,45 @@ class TestBOM(FrappeTestCase):
bom.update_cost()
self.assertFalse(bom.flags.cost_updated)
def test_do_not_include_manufacturing_and_fixed_items(self):
from erpnext.manufacturing.doctype.bom.bom import item_query
if not frappe.db.exists("Asset Category", "Computers-Test"):
doc = frappe.get_doc({"doctype": "Asset Category", "asset_category_name": "Computers-Test"})
doc.flags.ignore_mandatory = True
doc.insert()
for item_code, properties in {
"_Test RM Item 1 Do Not Include In Manufacture": {
"is_stock_item": 1,
"include_item_in_manufacturing": 0,
},
"_Test RM Item 2 Fixed Asset Item": {
"is_fixed_asset": 1,
"is_stock_item": 0,
"asset_category": "Computers-Test",
},
"_Test RM Item 3 Manufacture Item": {"is_stock_item": 1, "include_item_in_manufacturing": 1},
}.items():
make_item(item_code, properties)
data = item_query(
"Item",
txt="_Test RM Item",
searchfield="name",
start=0,
page_len=20000,
filters={"include_item_in_manufacturing": 1, "is_fixed_asset": 0},
)
items = []
for row in data:
items.append(row[0])
self.assertTrue("_Test RM Item 1 Do Not Include In Manufacture" not in items)
self.assertTrue("_Test RM Item 2 Fixed Asset Item" not in items)
self.assertTrue("_Test RM Item 3 Manufacture Item" in items)
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@@ -151,7 +151,7 @@ def queue_bom_cost_jobs(
while current_boms_list:
batch_no += 1
batch_size = 20_000
batch_size = 7_000
boms_to_process = current_boms_list[:batch_size] # slice out batch of 20k BOMs
# update list to exclude 20K (queued) BOMs

View File

@@ -420,7 +420,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2021-11-12 10:15:06.572401",
"modified": "2023-05-22 23:26:57.589331",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",

View File

@@ -657,7 +657,7 @@ class JobCard(Document):
self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
if self.docstatus < 2:
if self.for_quantity <= self.transferred_qty:
if flt(self.for_quantity) <= flt(self.transferred_qty):
self.status = "Material Transferred"
if self.time_logs:

View File

@@ -28,7 +28,7 @@
"fieldname": "qty",
"fieldtype": "Data",
"in_list_view": 1,
"label": "qty"
"label": "Qty"
},
{
"fieldname": "item_reference",
@@ -40,7 +40,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-07 17:03:49.707487",
"modified": "2023-03-31 10:30:14.604051",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Item Reference",
@@ -48,5 +48,6 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -592,20 +592,18 @@ erpnext.work_order = {
// all materials transferred for manufacturing, make this primary
finish_btn.addClass('btn-primary');
}
} else {
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
let allowance_percentage = doc.overproduction_percentage_for_work_order;
} else if (frm.doc.__onload && frm.doc.__onload.overproduction_percentage) {
let allowance_percentage = frm.doc.__onload.overproduction_percentage;
if (allowance_percentage > 0) {
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
if (allowance_percentage > 0) {
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
if ((flt(doc.produced_qty) < allowed_qty)) {
frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
}
if ((flt(doc.produced_qty) < allowed_qty)) {
frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
}
});
}
}
}
} else {

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