Compare commits

..

134 Commits

Author SHA1 Message Date
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
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
82 changed files with 1278 additions and 535 deletions

View File

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

View File

@@ -40,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",
@@ -293,6 +294,12 @@
"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",
@@ -300,7 +307,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-04-14 17:22:03.680886",
"modified": "2023-06-13 18:47:46.430291",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -330,12 +330,10 @@ class JournalEntry(AccountsController):
d.db_update()
def unlink_asset_reference(self):
if self.voucher_type != "Depreciation Entry":
return
for d in self.get("accounts"):
if (
d.reference_type == "Asset"
self.voucher_type == "Depreciation Entry"
and d.reference_type == "Asset"
and d.reference_name
and d.account_type == "Depreciation"
and d.debit
@@ -362,6 +360,15 @@ class JournalEntry(AccountsController):
else:
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"
)
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

@@ -623,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")},
@@ -653,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) {
@@ -684,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) {
@@ -708,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

@@ -150,19 +150,68 @@ 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:
@@ -391,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)),
@@ -1318,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"),
@@ -1354,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
@@ -1457,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 = []
@@ -1503,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

View File

@@ -999,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")

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

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

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

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

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

@@ -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
@@ -666,7 +667,7 @@ 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:
@@ -760,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": "",
@@ -812,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

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

@@ -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
@@ -339,13 +340,9 @@ 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,
@@ -369,9 +366,9 @@ class Asset(AccountsController):
# For first row
if (
(has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
n == 0
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
and not self.opening_accumulated_depreciation
and n == 0
):
from_date = add_days(
self.available_for_use_date, -1
@@ -383,10 +380,26 @@ class Asset(AccountsController):
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)
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 last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
@@ -411,9 +424,7 @@ class Asset(AccountsController):
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
@@ -432,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",
{
@@ -490,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:
@@ -1281,9 +1295,11 @@ def get_straight_line_or_manual_depr_amount(asset, row):
)
# if the Depreciation Schedule is being prepared for the first time
else:
return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
)
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(

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,
@@ -135,15 +136,15 @@ def make_depreciation_entry(asset_name, date=None):
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")
@@ -279,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)
@@ -343,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"):
@@ -403,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,
@@ -420,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,
@@ -453,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
@@ -491,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(
{
@@ -499,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,
}
)
@@ -523,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]

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

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

@@ -19,56 +19,6 @@ 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"),
@@ -89,22 +39,67 @@ frappe.query_reports["Fixed Asset Register"] = {
default: "--Select a group--",
reqd: 1
},
{
fieldname:"finance_book",
label: __("Finance Book"),
fieldtype: "Link",
options: "Finance Book",
depends_on: "eval: doc.filter_by_finance_book == 1",
},
{
fieldname:"filter_by_finance_book",
label: __("Filter by Finance Book"),
fieldtype: "Check"
},
{
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):
@@ -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,20 +101,31 @@ def get_data(filters):
]
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
assets_linked_to_fb = None
assets_linked_to_fb = get_assets_linked_to_fb(filters)
if filters.filter_by_finance_book:
assets_linked_to_fb = frappe.db.get_all(
doctype="Asset Finance Book",
filters={"finance_book": filters.finance_book or ("is", "not set")},
pluck="parent",
)
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 assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb:
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, filters.finance_book)
asset_value = get_asset_value_after_depreciation(
asset.asset_id, finance_book
) or get_asset_value_after_depreciation(asset.asset_id)
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
@@ -126,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,
@@ -140,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,
@@ -184,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():

View File

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

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

@@ -891,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)))

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

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

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

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

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

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

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

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

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

@@ -7,4 +7,6 @@ def execute():
frappe.reload_doc("manufacturing", "doctype", "work_order")
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")
frappe.db.sql(
"""UPDATE `tabWork Order Item` SET amount = ifnull(rate, 0.0) * ifnull(required_qty, 0.0)"""
)

View File

@@ -145,7 +145,6 @@ class AdditionalSalary(Document):
return amount_per_day * no_of_days
@frappe.whitelist()
def get_additional_salaries(employee, start_date, end_date, component_type):
from frappe.query_builder import Criterion

View File

@@ -80,22 +80,27 @@ frappe.ui.form.on("Salary Slip", {
},
currency: function(frm) {
frm.trigger("update_currency_changes");
},
update_currency_changes: function(frm) {
frm.trigger("set_exchange_rate");
frm.trigger("set_dynamic_labels");
},
set_dynamic_labels: function(frm) {
var company_currency = frm.doc.company? erpnext.get_currency(frm.doc.company): frappe.defaults.get_default("currency");
if (frm.doc.employee && frm.doc.currency) {
frappe.run_serially([
() => frm.events.set_exchange_rate(frm, company_currency),
() => frm.events.change_form_labels(frm, company_currency),
() => frm.events.change_grid_labels(frm),
() => frm.refresh_fields()
() => frm.events.change_form_labels(frm),
() => frm.events.change_grid_labels(frm),
() => frm.refresh_fields()
]);
}
},
set_exchange_rate: function(frm, company_currency) {
set_exchange_rate: function(frm) {
const company_currency = erpnext.get_currency(frm.doc.company);
if (frm.doc.docstatus === 0) {
if (frm.doc.currency) {
var from_currency = frm.doc.currency;
@@ -133,9 +138,11 @@ frappe.ui.form.on("Salary Slip", {
frm.set_df_property('section_break_43', 'hidden', 1);
},
change_form_labels: function(frm, company_currency) {
change_form_labels: function(frm) {
const company_currency = erpnext.get_currency(frm.doc.company);
frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction",
"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date", "gross_base_year_to_date"],
"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date", "base_gross_year_to_date"],
company_currency);
frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words", "year_to_date", "month_to_date", "gross_year_to_date"],
@@ -207,6 +214,9 @@ frappe.ui.form.on("Salary Slip", {
frm.fields_dict.absent_days.set_description(__("Unmarked Days is treated as {0}. You can can change this in {1}", [r.message, frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true)]));
}
frm.refresh();
// triggering events explicitly because structure is set on the server-side
// and currency is fetched from the structure
frm.trigger("update_currency_changes");
}
});
}

View File

@@ -1,17 +1,18 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import datetime
import math
from datetime import date
import frappe
from frappe import _, msgprint
from frappe.model.naming import make_autoname
from frappe.utils import (
add_days,
ceil,
cint,
cstr,
date_diff,
floor,
flt,
formatdate,
get_first_day,
@@ -57,8 +58,10 @@ class SalarySlip(TransactionBase):
"float": float,
"long": int,
"round": round,
"date": datetime.date,
"date": date,
"getdate": getdate,
"ceil": ceil,
"floor": floor,
}
def autoname(self):
@@ -653,15 +656,17 @@ class SalarySlip(TransactionBase):
amount = self.eval_condition_and_formula(struct_row, data)
if struct_row.statistical_component:
default_data[struct_row.abbr] = amount
# update statitical component amount in reference data based on payment days
# since row for statistical component is not added to salary slip
if struct_row.depends_on_payment_days:
joining_date, relieving_date = self.get_joining_and_relieving_dates()
default_data[struct_row.abbr] = amount
data[struct_row.abbr] = flt(
(flt(amount) * flt(self.payment_days) / cint(self.total_working_days)),
struct_row.precision("amount"),
payment_days_amount = (
flt(amount) * flt(self.payment_days) / cint(self.total_working_days)
if self.total_working_days
else 0
)
data[struct_row.abbr] = payment_days_amount
elif amount or struct_row.amount_based_on_formula and amount is not None:
default_amount = self.eval_condition_and_formula(struct_row, default_data)
@@ -957,7 +962,7 @@ class SalarySlip(TransactionBase):
tax_slab.allow_tax_exemption, payroll_period=payroll_period
)
future_structured_taxable_earnings = current_taxable_earnings.taxable_earnings * (
math.ceil(remaining_sub_periods) - 1
ceil(remaining_sub_periods) - 1
)
# get taxable_earnings, addition_earnings for current actual payment days

View File

@@ -183,13 +183,6 @@ def get_columns(earning_types, ded_types):
"fieldtype": "Float",
"width": 120,
},
{
"label": _("Currency"),
"fieldname": "currency",
"fieldtype": "Link",
"options": "Currency",
"hidden": 1,
},
]
for earning in earning_types:
@@ -247,6 +240,13 @@ def get_columns(earning_types, ded_types):
"options": "currency",
"width": 120,
},
{
"label": _("Currency"),
"fieldname": "currency",
"fieldtype": "Data",
"options": "Currency",
"hidden": 1,
},
]
)

View File

@@ -25,20 +25,38 @@ frappe.listview_settings['Task'] = {
}
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
},
gantt_custom_popup_html: function(ganttobj, task) {
var html = `<h5><a style="text-decoration:underline"\
href="/app/task/${ganttobj.id}""> ${ganttobj.name} </a></h5>`;
gantt_custom_popup_html: function (ganttobj, task) {
let html = `
<a class="text-white mb-2 inline-block cursor-pointer"
href="/app/task/${ganttobj.id}"">
${ganttobj.name}
</a>
`;
if(task.project) html += `<p>Project: ${task.project}</p>`;
html += `<p>Progress: ${ganttobj.progress}</p>`;
if (task.project) {
html += `<p class="mb-1">${__("Project")}:
<a class="text-white inline-block"
href="/app/project/${task.project}"">
${task.project}
</a>
</p>`;
}
html += `<p class="mb-1">
${__("Progress")}:
<span class="text-white">${ganttobj.progress}%</span>
</p>`;
if(task._assign_list) {
html += task._assign_list.reduce(
(html, user) => html + frappe.avatar(user)
, '');
if (task._assign) {
const assign_list = JSON.parse(task._assign);
const assignment_wrapper = `
<span>Assigned to:</span>
<span class="text-white">
${assign_list.map((user) => frappe.user_info(user).fullname).join(", ")}
</span>
`;
html += assignment_wrapper;
}
return html;
}
return `<div class="p-3" style="min-width: 220px">${html}</div>`;
},
};

View File

@@ -764,11 +764,13 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
precision("base_grand_total")
);
}
this.frm.doc.payments.find(pay => {
if (pay.default) {
pay.amount = total_amount_to_pay;
}
});
if(!this.frm.doc.is_return){
this.frm.doc.payments.find(payment => {
if (payment.default) {
payment.amount = total_amount_to_pay;
}
});
}
this.frm.refresh_fields();
},

View File

@@ -173,7 +173,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
this.frm.set_query("expense_account", "items", function(doc) {
return {
filters: {
"company": doc.company
"company": doc.company,
"report_type": "Profit and Loss",
"is_group": 0
}
};
});

View File

@@ -14,7 +14,8 @@
"section_break_4",
"account_number_length",
"column_break_6",
"temporary_against_account_number"
"temporary_against_account_number",
"opening_against_account_number"
],
"fields": [
{
@@ -70,14 +71,23 @@
},
{
"allow_in_quick_entry": 1,
"default": "9999",
"description": "Will be used as against account for all normal ledger entries",
"fieldname": "temporary_against_account_number",
"fieldtype": "Data",
"label": "Temporary Against Account Number",
"reqd": 1
},
{
"default": "9000",
"description": "Will be used as against account for opening ledger entries",
"fieldname": "opening_against_account_number",
"fieldtype": "Data",
"label": "Opening Against Account Number"
}
],
"links": [],
"modified": "2020-11-19 19:00:09.088816",
"modified": "2023-06-30 00:56:59.556731",
"modified_by": "Administrator",
"module": "Regional",
"name": "DATEV Settings",

View File

@@ -1,10 +1,26 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe import _, throw
from frappe.model.document import Document
class DATEVSettings(Document):
pass
def validate(self):
if (
self.temporary_against_account_number
and len(self.temporary_against_account_number) != self.account_number_length
):
throw(
_("Temporary Against Account Number must be {0} digits long").format(
self.account_number_length
)
)
if (
self.opening_against_account_number
and len(self.opening_against_account_number) != self.account_number_length
):
throw(
_("Opening Against Account Number must be {0} digits long").format(self.account_number_length)
)

View File

@@ -132,8 +132,12 @@ def execute(filters=None):
"""Entry point for frappe."""
data = []
if filters and validate(filters):
fn = "temporary_against_account_number"
filters[fn] = frappe.get_value("DATEV Settings", filters.get("company"), fn)
temp, opening = frappe.get_value(
"DATEV Settings",
filters.get("company"),
["temporary_against_account_number", "opening_against_account_number"],
)
filters.update({"against_account": temp, "opening_account": opening or temp})
data = get_transactions(filters, as_dict=0)
return COLUMNS, data
@@ -315,7 +319,7 @@ def run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=1):
acc.account_number as 'Konto',
/* against number or, if empty, party against number */
%(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)',
CASE gl.is_opening when 'Yes' then %(opening_account)s else %(against_account)s end as 'Gegenkonto (ohne BU-Schlüssel)',
'' as 'BU-Schlüssel',
@@ -530,18 +534,22 @@ def download_datev_csv(filters):
filters = json.loads(filters)
validate(filters)
company = filters.get("company")
fiscal_year = get_fiscal_year(date=filters.get("from_date"), company=company)
filters["fiscal_year_start"] = fiscal_year[1]
# set chart of accounts used
coa = frappe.get_value("Company", company, "chart_of_accounts")
filters["skr"] = "04" if "SKR04" in coa else ("03" if "SKR03" in coa else "")
datev_settings = frappe.get_doc("DATEV Settings", company)
filters["account_number_length"] = datev_settings.account_number_length
filters["temporary_against_account_number"] = datev_settings.temporary_against_account_number
filters.update(
{
"fiscal_year_start": fiscal_year[1],
"skr": "04" if "SKR04" in coa else ("03" if "SKR03" in coa else ""),
"account_number_length": datev_settings.account_number_length,
"against_account": datev_settings.temporary_against_account_number,
"opening_account": datev_settings.opening_against_account_number
or datev_settings.temporary_against_account_number,
}
)
transactions = get_transactions(filters)
account_names = get_account_names(filters)

View File

@@ -139,7 +139,9 @@ def make_datev_settings(company):
"client": company.name,
"client_number": "12345",
"consultant_number": "67890",
"account_number_length": 4,
"temporary_against_account_number": "9999",
"opening_against_account_number": "9000",
}
).insert()
@@ -152,7 +154,8 @@ class TestDatev(TestCase):
"company": self.company.name,
"from_date": today(),
"to_date": today(),
"temporary_against_account_number": "9999",
"against_account": "9999",
"opening_account": "9000",
}
make_datev_settings(self.company)

View File

@@ -21,16 +21,14 @@ def get_columns(filters):
columns = [
{
"label": _("Employee"),
"options": "Employee",
"fieldname": "employee",
"fieldtype": "Link",
"options": "Employee",
"width": 200,
},
{
"label": _("Employee Name"),
"options": "Employee",
"fieldname": "employee_name",
"fieldtype": "Link",
"width": 160,
},
{"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "width": 140},

View File

@@ -18,16 +18,14 @@ def get_columns(filters):
columns = [
{
"label": _("Employee"),
"options": "Employee",
"fieldname": "employee",
"fieldtype": "Link",
"options": "Employee",
"width": 200,
},
{
"label": _("Employee Name"),
"options": "Employee",
"fieldname": "employee_name",
"fieldtype": "Link",
"width": 160,
},
{"label": _("PF Account"), "fieldname": "pf_account", "fieldtype": "Data", "width": 140},

View File

@@ -35,6 +35,7 @@ def add_print_formats():
def add_permissions():
frappe.reload_doc("regional", "doctype", "ksa_vat_setting", force=True)
"""Add Permissions for KSA VAT Setting."""
add_permission("KSA VAT Setting", "All", 0)
for role in ("Accounts Manager", "Accounts User", "System Manager"):

View File

@@ -656,11 +656,15 @@ def get_credit_limit(customer, company):
if not credit_limit:
customer_group = frappe.get_cached_value("Customer", customer, "customer_group")
credit_limit = frappe.db.get_value(
result = frappe.db.get_values(
"Customer Credit Limit",
{"parent": customer_group, "parenttype": "Customer Group", "company": company},
"credit_limit",
fieldname=["credit_limit", "bypass_credit_limit_check"],
as_dict=True,
)
if result and not result[0].bypass_credit_limit_check:
credit_limit = result[0].credit_limit
if not credit_limit:
credit_limit = frappe.get_cached_value("Company", company, "credit_limit")

View File

@@ -235,7 +235,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
}
}
// payment request
if(flt(doc.per_billed)<100) {
if(flt(doc.per_billed, precision('per_billed', doc)) < 100 + frappe.boot.sysdefaults.over_billing_allowance) {
this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create'));
this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create'));
}

View File

@@ -22,6 +22,10 @@ def boot_session(bootinfo):
bootinfo.sysdefaults.allow_stale = cint(
frappe.db.get_single_value("Accounts Settings", "allow_stale")
)
bootinfo.sysdefaults.over_billing_allowance = frappe.db.get_single_value(
"Accounts Settings", "over_billing_allowance"
)
bootinfo.sysdefaults.quotation_valid_till = cint(
frappe.db.get_single_value("Selling Settings", "default_valid_till")
)

View File

@@ -292,7 +292,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty
)
while remaining_stock_qty > 0 and available_locations:
while flt(remaining_stock_qty) > 0 and available_locations:
item_location = available_locations.pop(0)
item_location = frappe._dict(item_location)

View File

@@ -113,6 +113,7 @@ class PurchaseReceipt(BuyingController):
self.set_status()
self.po_required()
self.validate_items_quality_inspection()
self.validate_with_previous_doc()
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
self.validate_uom_is_integer("stock_uom", "stock_qty")
@@ -183,6 +184,26 @@ class PurchaseReceipt(BuyingController):
if not d.purchase_order:
frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code))
def validate_items_quality_inspection(self):
for item in self.get("items"):
if item.quality_inspection:
qi = frappe.db.get_value(
"Quality Inspection",
item.quality_inspection,
["reference_type", "reference_name", "item_code"],
as_dict=True,
)
if qi.reference_type != self.doctype or qi.reference_name != self.name:
msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Reference Type
{frappe.bold(self.doctype)} and Reference Name {frappe.bold(self.name)}."""
frappe.throw(_(msg))
if qi.item_code != item.item_code:
msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Item Code
{frappe.bold(item.item_code)}."""
frappe.throw(_(msg))
def get_already_received_qty(self, po, po_detail):
qty = frappe.db.sql(
"""select sum(qty) from `tabPurchase Receipt Item`

View File

@@ -1849,6 +1849,121 @@ class TestPurchaseReceipt(FrappeTestCase):
pr.items[0].delivery_note_item = delivery_note_item
pr.save()
def test_purchase_receipt_with_backdated_landed_cost_voucher(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
create_landed_cost_voucher,
)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
item_code = "_Test Purchase Item With Landed Cost"
create_item(item_code)
warehouse = create_warehouse("_Test Purchase Warehouse With Landed Cost")
warehouse1 = create_warehouse("_Test Purchase Warehouse With Landed Cost 1")
warehouse2 = create_warehouse("_Test Purchase Warehouse With Landed Cost 2")
warehouse3 = create_warehouse("_Test Purchase Warehouse With Landed Cost 3")
pr = make_purchase_receipt(
item_code=item_code,
warehouse=warehouse,
posting_date=add_days(today(), -10),
posting_time="10:59:59",
qty=100,
rate=275.00,
)
pr_return = make_return_doc("Purchase Receipt", pr.name)
pr_return.posting_date = add_days(today(), -9)
pr_return.items[0].qty = 2 * -1
pr_return.items[0].received_qty = 2 * -1
pr_return.submit()
ste1 = make_stock_entry(
purpose="Material Transfer",
posting_date=add_days(today(), -8),
source=warehouse,
target=warehouse1,
item_code=item_code,
qty=20,
company=pr.company,
)
ste1.reload()
self.assertEqual(ste1.items[0].valuation_rate, 275.00)
ste2 = make_stock_entry(
purpose="Material Transfer",
posting_date=add_days(today(), -7),
source=warehouse,
target=warehouse2,
item_code=item_code,
qty=20,
company=pr.company,
)
ste2.reload()
self.assertEqual(ste2.items[0].valuation_rate, 275.00)
ste3 = make_stock_entry(
purpose="Material Transfer",
posting_date=add_days(today(), -6),
source=warehouse,
target=warehouse3,
item_code=item_code,
qty=20,
company=pr.company,
)
ste3.reload()
self.assertEqual(ste3.items[0].valuation_rate, 275.00)
ste4 = make_stock_entry(
purpose="Material Transfer",
posting_date=add_days(today(), -5),
source=warehouse1,
target=warehouse,
item_code=item_code,
qty=20,
company=pr.company,
)
ste4.reload()
self.assertEqual(ste4.items[0].valuation_rate, 275.00)
ste5 = make_stock_entry(
purpose="Material Transfer",
posting_date=add_days(today(), -4),
source=warehouse,
target=warehouse1,
item_code=item_code,
qty=20,
company=pr.company,
)
ste5.reload()
self.assertEqual(ste5.items[0].valuation_rate, 275.00)
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2500 * -1)
pr.reload()
valuation_rate = pr.items[0].valuation_rate
ste1.reload()
self.assertEqual(ste1.items[0].valuation_rate, valuation_rate)
ste2.reload()
self.assertEqual(ste2.items[0].valuation_rate, valuation_rate)
ste3.reload()
self.assertEqual(ste3.items[0].valuation_rate, valuation_rate)
ste4.reload()
self.assertEqual(ste4.items[0].valuation_rate, valuation_rate)
ste5.reload()
self.assertEqual(ste5.items[0].valuation_rate, valuation_rate)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -204,6 +204,7 @@
"fieldname": "received_qty",
"fieldtype": "Float",
"label": "Received Quantity",
"no_copy": 1,
"oldfieldname": "received_qty",
"oldfieldtype": "Currency",
"print_hide": 1,
@@ -993,7 +994,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-10-12 03:37:59.516609",
"modified": "2023-07-02 18:40:48.152637",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
@@ -1004,4 +1005,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -294,3 +294,19 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
self.assertRaises(frappe.ValidationError, riv.save)
accounts_settings.acc_frozen_upto = ""
accounts_settings.save()
def test_create_repost_entry_for_cancelled_document(self):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
get_multiple_items=True,
)
self.assertTrue(pr.docstatus == 1)
self.assertFalse(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name}))
pr.load_from_db()
pr.cancel()
self.assertTrue(pr.docstatus == 2)
self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name}))

View File

@@ -122,7 +122,8 @@
"oldfieldname": "purpose",
"oldfieldtype": "Select",
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "company",
@@ -619,7 +620,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-05-02 05:21:39.060501",
"modified": "2023-06-19 18:23:40.748114",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",

View File

@@ -2271,7 +2271,7 @@ def move_sample_to_retention_warehouse(company, items):
"basic_rate": item.get("valuation_rate"),
"uom": item.get("uom"),
"stock_uom": item.get("stock_uom"),
"conversion_factor": 1.0,
"conversion_factor": item.get("conversion_factor") or 1.0,
"serial_no": sample_serial_nos,
"batch_no": item.get("batch_no"),
},

View File

@@ -13,10 +13,11 @@ frappe.ui.form.on("Warehouse", {
};
});
frm.set_query("parent_warehouse", function () {
frm.set_query("parent_warehouse", function (doc) {
return {
filters: {
is_group: 1,
company: doc.company,
},
};
});

View File

@@ -96,14 +96,14 @@ def get_range_age(filters: Filters, fifo_queue: List, to_date: str, item_dict: D
range1 = range2 = range3 = above_range3 = 0.0
for item in fifo_queue:
age = date_diff(to_date, item[1])
age = flt(date_diff(to_date, item[1]))
qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0
if age <= filters.range1:
if age <= flt(filters.range1):
range1 = flt(range1 + qty, precision)
elif age <= filters.range2:
elif age <= flt(filters.range2):
range2 = flt(range2 + qty, precision)
elif age <= filters.range3:
elif age <= flt(filters.range3):
range3 = flt(range3 + qty, precision)
else:
above_range3 = flt(above_range3 + qty, precision)

View File

@@ -502,6 +502,7 @@ class update_entries_after(object):
def update_distinct_item_warehouses(self, dependant_sle):
key = (dependant_sle.item_code, dependant_sle.warehouse)
val = frappe._dict({"sle": dependant_sle})
if key not in self.distinct_item_warehouses:
self.distinct_item_warehouses[key] = val
self.new_items_found = True
@@ -513,6 +514,9 @@ class update_entries_after(object):
val.sle_changed = True
self.distinct_item_warehouses[key] = val
self.new_items_found = True
elif self.distinct_item_warehouses[key].get("reposting_status"):
self.distinct_item_warehouses[key] = val
self.new_items_found = True
def process_sle(self, sle):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -1154,6 +1158,8 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
[
"item_code",
"warehouse",
"actual_qty",
"qty_after_transaction",
"posting_date",
"posting_time",
"timestamp(posting_date, posting_time) as timestamp",

View File

@@ -9896,3 +9896,11 @@ Total Equity,Eigenkapital,
Warehouse wise Stock Value,Warenwert nach Lager,
Discount Validity,Frist für den Rabatt,
Discount Validity Based On,Frist für den Rabatt berechnet sich nach,
Account Number Length,Kontonummer Länge,
Temporary Against Account Number,Temporäre Gegenkontonummer,
Change DATEV Settings,DATEV-Einstellungen ändern,
Opening Against Account Number,Gegenkontonummer für Eröffnungsbuchungen,
Will be used as against account for all normal ledger entries,Wird als Gegenkonto für alle normalen Buchungen verwendet,
Will be used as against account for opening ledger entries,Wird als Gegenkonto für alle Eröffnungsbuchungen verwendet,
Temporary Against Account Number must be {0} digits long,Temporäre Gegenkontonummer muss {0} Ziffern lang sein,
Opening Against Account Number must be {0} digits long,Gegenkontonummer für Eröffnungsbuchungen muss {0} Ziffern lang sein,
Can't render this file because it is too large.

View File

@@ -875,7 +875,7 @@ Donor,schenker,
Donor Type information.,Donor Type informatie.,
Donor information.,Donorinformatie.,
Download JSON,JSON downloaden,
Draft,Droogte,
Draft,Concept,
Drop Ship,Drop Ship,
Drug,drug,
Due / Reference Date cannot be after {0},Verval- / Referentiedatum kan niet na {0} zijn,
@@ -4280,7 +4280,7 @@ Failed to setup defaults for country {0}. Please contact support@erpnext.com,Kan
Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.,Rij # {0}: artikel {1} is geen geserialiseerd / batch artikel. Het kan geen serienummer / batchnummer hebben.,
Please set {0},Stel {0} in,
Please set {0},Stel {0} in,supplier
Draft,Droogte,"docstatus,=,0"
Draft,Concept,"docstatus,=,0"
Cancelled,Geannuleerd,"docstatus,=,2"
Please setup Instructor Naming System in Education > Education Settings,Stel het instructeursysteem in onder onderwijs&gt; onderwijsinstellingen,
Please set Naming Series for {0} via Setup > Settings > Naming Series,Stel Naming Series in op {0} via Instellingen&gt; Instellingen&gt; Naming Series,
@@ -8192,7 +8192,7 @@ Actual Batch Quantity,Werkelijke batchhoeveelheid,
Prevdoc DocType,Prevdoc DocType,
Parent Detail docname,Bovenliggende Detail docname,
"Generate packing slips for packages to be delivered. Used to notify package number, package contents and its weight.","Genereren van pakbonnen voor pakketten te leveren. Gebruikt voor pakket nummer, inhoud van de verpakking en het gewicht te melden.",
Indicates that the package is a part of this delivery (Only Draft),Geeft aan dat het pakket een onderdeel is van deze levering (alleen ontwerp),
Indicates that the package is a part of this delivery (Only Draft),Geeft aan dat het pakket een onderdeel is van deze levering (alleen concept),
MAT-PAC-.YYYY.-,MAT-PAC-.YYYY.-,
From Package No.,Van Pakket No,
Identification of the package for the delivery (for print),Identificatie van het pakket voor de levering (voor afdrukken),
Can't render this file because it is too large.

View File

@@ -1,7 +1,7 @@
# frappe # https://github.com/frappe/frappe is installed during bench-init
gocardless-pro~=1.22.0
googlemaps # used in ERPNext, but dependency is defined in Frappe
pandas~=1.1.5
pandas>=1.1.5,<2.0.0
plaid-python~=7.2.1
pycountry~=20.7.3
PyGithub~=1.54.1
@@ -10,4 +10,4 @@ python-youtube~=0.8.0
taxjar~=1.9.2
tweepy~=3.10.0
Unidecode~=1.2.0
redisearch==2.0.0
redisearch==2.0.0