Compare commits

...

276 Commits

Author SHA1 Message Date
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
Sagar Sharma
4a9ad09c7f Merge pull request #35284 from frappe/mergify/bp/version-13-hotfix/pr-35275
fix: add missing options for `Content Align` (backport #35275)
2023-05-13 09:41:55 +05:30
Sagar Sharma
e37b9030fb fix: add missing options for Content Align
(cherry picked from commit d16caa2d2c)
2023-05-13 04:05:15 +00:00
Saurabh
77f548c814 fix: update reference data for statistical component 2023-05-12 12:10:40 +05:30
Frappe PR Bot
7626d51db1 chore(release): Bumped to Version 13.50.3
## [13.50.3](https://github.com/frappe/erpnext/compare/v13.50.2...v13.50.3) (2023-05-11)

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

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

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

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

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

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

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

### Bug Fixes

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

### Features

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

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

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

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

(cherry picked from commit 80230fec3e)

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

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

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

(cherry picked from commit f3b3dabb9a)

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

fix: per_billed condition for Payment Entry (#34969)

(cherry picked from commit d6bc8bba8b)

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

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

### Bug Fixes

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

(cherry picked from commit ecea9b44a3)

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

(cherry picked from commit f7b50f2ade)

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

(cherry picked from commit dc178984ae)

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

* chore: Update test case

(cherry picked from commit e25b98b620)

* chore: More fixes

(cherry picked from commit 31c95deb88)

* chore: resolve conflicts

---------

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

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

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

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

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

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

* chore: add validation for depr journal entry

* test: manual_depr_for_depreciable_asset and manual_depr_w_incorrect_jv_voucher_type

* chore: unlink depreciable asset from manual depr entry

(cherry picked from commit 3c75e55cb9)

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

* chore: fixed conflicts

---------

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

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

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

(cherry picked from commit 5c75894065)

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

* chore: resolve conflicts

* chore: fix tests

---------

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

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

### Bug Fixes

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

[skip ci]

(cherry picked from commit dd93ea067e)

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

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

[skip ci]

(cherry picked from commit aa8b241d5a)

# Conflicts:
#	CODEOWNERS

* fix: conflicts

* chore: remove duplicates

* chore: remove duplicates

---------

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

### Bug Fixes

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

### Reverts

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

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

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

(cherry picked from commit c957a5cd2e)

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

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

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

### Reverts

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

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

This reverts commit b48fca3e5a.

(cherry picked from commit 034e35e7f6)

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

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

* fix: Shop by category fixes

* chore: Update tests

(cherry picked from commit 56f5078357)

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

* fix: require sender and message for contact us page

* refactor: dont override frappe.send_message from client side

used override_whitelisted_method hook for the same

(cherry picked from commit f193393f57)

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

(cherry picked from commit 3896d41e95)

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

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

(cherry picked from commit 768c3a4927)

* fix: Recalculate difference amount after setting deductions

(cherry picked from commit 75ec0a0a85)

* fix: Set deductions in base currency

- Use field precision to get more accurate values

(cherry picked from commit dc2998f544)

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

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

(cherry picked from commit 2ae5834290)

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

(cherry picked from commit c217bb2018)

* fix: Set deduction amount in company currency on Doctype

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

(cherry picked from commit 7f2e7badff)

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

- misc: better docstring

(cherry picked from commit f02fc8acf0)

* fix: Paid amount must be discounted considering accounting currency

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

(cherry picked from commit 761f68d7bf)

* fix: Multi-currency SI with base currency PE

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

(cherry picked from commit b09c2381ca)

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

(cherry picked from commit 9abf0ef615)

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

* fix: Handle rounding more gracefully

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

(cherry picked from commit caa1a3dccf)

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

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

(cherry picked from commit d6d0163514)

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

* feat: Make Tax loss booking optional

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

(cherry picked from commit 216a46bd66)

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

* fix: Merge conflicts

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

- Make sure check uses this field only for eligible documents

---------

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

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

* chore: add period

* chore: improve msg

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

(cherry picked from commit af3e807607)

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

* Update asset.json

---------

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

### Bug Fixes

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

* fix: lost opportunity report issue

* chore: Linting Issues

---------

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

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

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

### Bug Fixes

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

(cherry picked from commit b04a101c11)

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

* chore: `conflicts`

---------

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

(cherry picked from commit 12ad2aa2e5)

# Conflicts:
#	erpnext/controllers/status_updater.py

* chore: resolve conflicts

---------

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

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

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

(cherry picked from commit 46d148defd)

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

(cherry picked from commit 5f84993bae)

* fix: not able to select customer / supplier

---------

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

### Bug Fixes

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

### Bug Fixes

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

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

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

---------

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

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

### Bug Fixes

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

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

(cherry picked from commit abf9a28d6a)

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

(cherry picked from commit f5937f46cb)

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

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

(cherry picked from commit f3993783a3)

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

(cherry picked from commit fc1088d9c4)

* feat: consider `over_order_allowance` while validating order qty

(cherry picked from commit 8bcbc45add)

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

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

(cherry picked from commit d7da8928ac)

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

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

(cherry picked from commit 53701c37b1)

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

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

(cherry picked from commit 66f650061d)

* chore: `conflicts`

---------

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

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

(cherry picked from commit abf9a28d6a)

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

(cherry picked from commit f5937f46cb)

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

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

(cherry picked from commit f3993783a3)

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

(cherry picked from commit fc1088d9c4)

* feat: consider `over_order_allowance` while validating order qty

(cherry picked from commit 8bcbc45add)

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

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

(cherry picked from commit d7da8928ac)

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

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

(cherry picked from commit 53701c37b1)

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

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

(cherry picked from commit 66f650061d)

* chore: `conflicts`

---------

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

* chore: Update user manual link (#34478)

(cherry picked from commit be723bb9d4)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

* chore: Update version

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

* chore: Update version

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

---------

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

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

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

(cherry picked from commit e2f19c6a14)

* refactor: Ignore linked Cr Notes in Report output

(cherry picked from commit d0715a82eb)

* test: Gross Profit report output for Cr notes

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

(cherry picked from commit cc61daeec4)

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

* fix: incorrect schedule after repair for WDV and DD

* chore: only fix schedules for assets with calc_depr true

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

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

refactor: minor asset repair of calc depr and if statement

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

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

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

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

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

* fix: german translations (#34312)

fix: german translations (#34312)

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

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

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

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

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

* test: cancelling advance should remove exchange gain/loss

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

---------

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

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

* test: cancelling advance should remove exchange gain/loss

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

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

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

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

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

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

* chore: only fix schedules for assets with calc_depr true

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

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

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

(cherry picked from commit be723bb9d4)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

* chore: Update version

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

* chore: Update version

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

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-03-19 18:08:15 +05:30
Frappe PR Bot
cfcbdfcaec chore(release): Bumped to Version 13.49.5
## [13.49.5](https://github.com/frappe/erpnext/compare/v13.49.4...v13.49.5) (2023-03-14)

### Bug Fixes

* `BOM Stock Report` ([eb1f8f9](eb1f8f932d))
* `required_qty` get reset to `1` for Alternative Item in WO ([d117101](d1171016b3))
* adjust excess cf leaves in new leaves taken if number exceeds cf leaves allocation ([bc12269](bc12269ef4))
* consider leaves taken while calculating expired carry-forwarded leaves ([e74e02b](e74e02b765))
* consider leaves taken within carry-forwarded period separately while calculating balance ([52108d5](52108d52e2))
* consider relieving date while calculating payment days based on lwp ([563f83f](563f83f0f5))
* Don't use get_list & get_all interchangeably ([b70a37f](b70a37f6fa))
* Error in consolidated financial statement ([#34330](https://github.com/frappe/erpnext/issues/34330)) ([470dc10](470dc10b15))
* exclude cancelled leave ledger entries ([91cad9e](91cad9e985))
* exclude carry forwarding leaves while updating leaves after submission ([88c5de5](88c5de533a))
* leave allocation tests ([2f62a96](2f62a9641e))
* linter ([341eab2](341eab2b2a))
* operation time for multi-level BOM in WO ([f4d07cc](f4d07cc84e))
* precision for newly allocated leaves ([238769e](238769e6b5))
* **test:** `get_leave_allocation_records` ([c01bed9](c01bed9862))
* **test:** `TestBomStockReport` ([4824302](4824302811))
* Total row in trail balance report (backport [#34395](https://github.com/frappe/erpnext/issues/34395)) ([#34431](https://github.com/frappe/erpnext/issues/34431)) ([809d6d6](809d6d638e))
* Use customer name instead of name(id) in PSOA (backport [#34412](https://github.com/frappe/erpnext/issues/34412)) ([#34426](https://github.com/frappe/erpnext/issues/34426)) ([a24f050](a24f0507e1))

### Performance Improvements

* `update_completed_qty()` in `material_request.py` ([5bc2b8f](5bc2b8f685))
* Stock Entry (Material Transfer) ([7a159a7](7a159a7187))

### Reverts

* Revert "fix: Default sales team not getting set" (#34376) ([42bda6e](42bda6e37b)), closes [#34376](https://github.com/frappe/erpnext/issues/34376) [#34376](https://github.com/frappe/erpnext/issues/34376) [#34284](https://github.com/frappe/erpnext/issues/34284)
2023-03-14 16:38:26 +00:00
Deepesh Garg
499987040b Merge pull request #34441 from frappe/version-13-hotfix
chore: release v13
2023-03-14 22:06:33 +05:30
Sagar Sharma
28027a9f94 Merge pull request #34449 from frappe/mergify/bp/version-13-hotfix/pr-34415
fix: operation time for multi-level BOM in WO (backport #34415)
2023-03-14 19:25:44 +05:30
Sagar Sharma
b4aabf3f35 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-34415 2023-03-14 18:55:49 +05:30
Sagar Sharma
cf49b0effb Merge pull request #34451 from frappe/mergify/bp/version-13-hotfix/pr-34381
chore: fix french translation (backport #34381)
2023-03-14 18:55:19 +05:30
HENRY Florian
0bb43a1be5 chore: fix french translation (#34381)
chore: update french translation
(cherry picked from commit d267111e13)
2023-03-14 13:20:05 +00:00
s-aga-r
f4d07cc84e fix: operation time for multi-level BOM in WO
(cherry picked from commit 442ee3adba)
2023-03-14 13:18:29 +00:00
mergify[bot]
809d6d638e fix: Total row in trail balance report (backport #34395) (#34431)
fix: Total row in trail balance report (#34395)

* fix: Total row in trail balance report

* fix: Calculate total after preparing opening and closing

(cherry picked from commit c6999fc687)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-14 15:07:30 +05:30
Rucha Mahabal
0834cb1bb6 Merge pull request #34436 from ruchamahabal/fix-leave-balance-editing-v13 2023-03-14 12:57:52 +05:30
Rucha Mahabal
2f62a9641e fix: leave allocation tests 2023-03-14 12:24:56 +05:30
Rucha Mahabal
cdf73bb781 test: update for total leaves allocated post submission 2023-03-14 11:49:32 +05:30
Rucha Mahabal
238769e6b5 fix: precision for newly allocated leaves 2023-03-14 11:49:24 +05:30
Rucha Mahabal
91cad9e985 fix: exclude cancelled leave ledger entries 2023-03-14 11:49:14 +05:30
Rucha Mahabal
7b9784ce10 refactor(tests): create_leave_type usage 2023-03-14 11:49:02 +05:30
Rucha Mahabal
fc10c8e44e test: leaves updated after submission with carry forwarding 2023-03-14 11:48:53 +05:30
Rucha Mahabal
88c5de533a fix: exclude carry forwarding leaves while updating leaves after submission 2023-03-14 11:48:42 +05:30
mergify[bot]
a24f0507e1 fix: Use customer name instead of name(id) in PSOA (backport #34412) (#34426)
fix: Use customer name instead of name(id) in PSOA (#34412)

(cherry picked from commit fa776d2987)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-13 20:29:17 +05:30
mergify[bot]
470dc10b15 fix: Error in consolidated financial statement (#34330)
fix: Error in consolidated financial statement (#34330)

(cherry picked from commit aae53bb910)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-13 14:02:28 +05:30
Saurabh
4d25091196 Merge pull request #34359 from saurabh6790/consider-relieving-date-in-lwp-calculation
fix: consider relieving date while calculating payment days based on lwp
2023-03-13 13:11:28 +05:30
Saurabh
44df522655 chore: fix linter #nosemgrep 2023-03-13 12:43:20 +05:30
Rucha Mahabal
8694d22b7a Merge pull request #34388 from ruchamahabal/fix-balance-edge-cases-v13 2023-03-10 14:39:03 +05:30
Rucha Mahabal
072c7e913d chore: fix linter 2023-03-10 11:44:33 +05:30
Rucha Mahabal
bc12269ef4 fix: adjust excess cf leaves in new leaves taken if number exceeds cf leaves allocation 2023-03-10 11:41:36 +05:30
Rucha Mahabal
7717a8a5e3 test: leave details with application across and after cf leave expiry 2023-03-10 11:41:17 +05:30
Rucha Mahabal
fd5d2ed87f refactor: consider cases for partially consumed cf and new leaves
- the above two cases weren't considering the split between cf leaves taken and new leaves taken and substracting all consumed leaves from cf leaves
2023-03-10 11:29:18 +05:30
Rucha Mahabal
c01bed9862 fix(test): get_leave_allocation_records 2023-03-10 11:29:12 +05:30
Rucha Mahabal
52108d52e2 fix: consider leaves taken within carry-forwarded period separately while calculating balance 2023-03-10 11:29:04 +05:30
Rucha Mahabal
e74e02b765 fix: consider leaves taken while calculating expired carry-forwarded leaves 2023-03-10 11:28:56 +05:30
gavin
b70a37f6fa fix: Don't use get_list & get_all interchangeably
* fix: Fetch all fields via get_returned_qty_map_for_row

* fix(update_billing_percentage): Remove permlevel checks on aggregated
value
2023-03-09 16:05:54 +05:30
Frappe PR Bot
5157f5dd0e chore(release): Bumped to Version 13.49.4
## [13.49.4](https://github.com/frappe/erpnext/compare/v13.49.3...v13.49.4) (2023-03-09)

### Reverts

* Revert "fix: Default sales team not getting set" (#34376) ([b712aea](b712aea3a4)), closes [#34376](https://github.com/frappe/erpnext/issues/34376) [#34376](https://github.com/frappe/erpnext/issues/34376) [#34284](https://github.com/frappe/erpnext/issues/34284)
2023-03-09 10:26:41 +00:00
Deepesh Garg
9ce5d84951 Merge pull request #34380 from frappe/mergify/bp/version-13/pr-34378
Revert "fix: Default sales team not getting set" (backport #34376) (backport #34378)
2023-03-09 15:43:02 +05:30
mergify[bot]
b712aea3a4 Revert "fix: Default sales team not getting set" (#34376)
Revert "fix: Default sales team not getting set" (#34376)

Revert "fix: Default sales team not getting set (#34284)"

This reverts commit 7d0199d743.

(cherry picked from commit 9a8f8e8b7d)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 42bda6e37b)
2023-03-09 10:12:06 +00:00
mergify[bot]
42bda6e37b Revert "fix: Default sales team not getting set" (#34376)
Revert "fix: Default sales team not getting set" (#34376)

Revert "fix: Default sales team not getting set (#34284)"

This reverts commit 7d0199d743.

(cherry picked from commit 9a8f8e8b7d)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-09 15:40:32 +05:30
Sagar Sharma
eb24f91341 Merge pull request #34369 from frappe/mergify/bp/version-13-hotfix/pr-34360
chore: `Alternative Item Code` error msg (backport #34360)
2023-03-09 13:54:59 +05:30
Saurabh
341eab2b2a fix: linter 2023-03-09 13:24:38 +05:30
Sagar Sharma
0857632359 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-34360 2023-03-09 12:07:45 +05:30
Sagar Sharma
e23f1555bb Merge pull request #34367 from frappe/mergify/bp/version-13-hotfix/pr-34362
fix: `required_qty` get reset to `1` for Alternative Item in WO (backport #34362)
2023-03-09 11:09:06 +05:30
s-aga-r
75c844a15a chore: Alternative Item Code error msg
(cherry picked from commit baef5ae1ef)
2023-03-09 04:38:21 +00:00
s-aga-r
d1171016b3 fix: required_qty get reset to 1 for Alternative Item in WO
(cherry picked from commit 046834a97a)
2023-03-09 04:35:58 +00:00
Saurabh
563f83f0f5 fix: consider relieving date while calculating payment days based on lwp 2023-03-08 19:02:46 +05:30
Sagar Sharma
9844508066 Merge pull request #34357 from frappe/mergify/bp/version-13-hotfix/pr-34352
fix: `BOM Stock Report` (backport #34352)
2023-03-08 18:14:42 +05:30
s-aga-r
4824302811 fix(test): TestBomStockReport 2023-03-08 17:44:51 +05:30
s-aga-r
08b9aaff26 test: add test cases for BOM Stock Report
(cherry picked from commit b53dcb04ed)
2023-03-08 10:56:43 +00:00
s-aga-r
eb1f8f932d fix: BOM Stock Report
(cherry picked from commit a65b80911b)
2023-03-08 10:56:43 +00:00
Frappe PR Bot
07ff956fd8 chore(release): Bumped to Version 13.49.3
## [13.49.3](https://github.com/frappe/erpnext/compare/v13.49.2...v13.49.3) (2023-03-07)

### Performance Improvements

* `update_completed_qty()` in `material_request.py` ([6841e22](6841e22ffe))
* Stock Entry (Material Transfer) ([56a422d](56a422deed))
2023-03-07 17:01:01 +00:00
Sagar Sharma
c575942acf Merge pull request #34341 from frappe/mergify/bp/version-13/pr-34336
perf: Stock Entry (Material Transfer) (backport #34313) (backport #34336)
2023-03-07 22:29:01 +05:30
s-aga-r
6841e22ffe perf: update_completed_qty() in material_request.py
(cherry picked from commit 8ad9e99cea)
(cherry picked from commit 5bc2b8f685)
2023-03-07 16:07:23 +00:00
s-aga-r
56a422deed perf: Stock Entry (Material Transfer)
(cherry picked from commit de18f98c5c)
(cherry picked from commit 7a159a7187)
2023-03-07 16:07:22 +00:00
Sagar Sharma
99b201d5d7 Merge pull request #34336 from frappe/mergify/bp/version-13-hotfix/pr-34313
perf: Stock Entry (Material Transfer) (backport #34313)
2023-03-07 21:34:09 +05:30
s-aga-r
5bc2b8f685 perf: update_completed_qty() in material_request.py
(cherry picked from commit 8ad9e99cea)
2023-03-07 12:33:46 +00:00
s-aga-r
7a159a7187 perf: Stock Entry (Material Transfer)
(cherry picked from commit de18f98c5c)
2023-03-07 12:33:46 +00:00
Frappe PR Bot
0ec34e5880 chore(release): Bumped to Version 13.49.2
## [13.49.2](https://github.com/frappe/erpnext/compare/v13.49.1...v13.49.2) (2023-03-07)

### Bug Fixes

* `rejected_serial_no` not getting copied from PR to PR(Return) ([bb55210](bb55210f49))
* `Serial No is mandatory` even if the `qty` is `0` ([9bea2fc](9bea2fcdfc))
* Default sales team not getting set ([#34284](https://github.com/frappe/erpnext/issues/34284)) ([65c0189](65c0189c4d))
* **minor:** Dirty the form after clicking on Get advances button in Invoices ([#34323](https://github.com/frappe/erpnext/issues/34323)) ([3a1475a](3a1475a90b))
* **test:** check for batch_no in returned dict ([8c5322c](8c5322c1cb))
* UI freeze while selecting batched items in sales invoice ([82a8f2b](82a8f2b1b2))
* Wrap unexpectedly long text in remark ([ba66a67](ba66a6714c))
2023-03-07 10:43:39 +00:00
Deepesh Garg
ba58c7ed59 Merge pull request #34326 from frappe/version-13-hotfix
chore: release v13
2023-03-07 16:11:57 +05:30
mergify[bot]
3a1475a90b fix(minor): Dirty the form after clicking on Get advances button in Invoices (#34323)
fix(minor): Dirty the form after clicking on Get advances button in Invoices (#34323)

fix(minor): Dirty form after clicking on Get advances button

(cherry picked from commit 2feb27e399)

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2023-03-07 15:47:37 +05:30
mergify[bot]
65c0189c4d fix: Default sales team not getting set (#34284)
fix: Default sales team not getting set (#34284)

(cherry picked from commit 7d0199d743)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-07 13:33:55 +05:30
mergify[bot]
1b2c4bf868 chore: add german translations (#34167)
* chore: add german translations (#34167)

* chore: add german translations

* Apply suggestions from code review

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

---------

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

# Conflicts:
#	erpnext/translations/de.csv

* chore: resolve conflicts

---------

Co-authored-by: Patrick Eissler <77415730+PatrickDenis-stack@users.noreply.github.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-03-07 11:46:30 +05:30
ruthra kumar
88ed6e6cb4 Merge pull request #34304 from ruthra-kumar/ui_freeze_on_item_selection
fix: UI freeze while selecting batched items in sales invoice
2023-03-06 11:24:01 +05:30
ruthra kumar
8c5322c1cb fix(test): check for batch_no in returned dict 2023-03-05 20:47:31 +05:30
ruthra kumar
82a8f2b1b2 fix: UI freeze while selecting batched items in sales invoice 2023-03-05 20:47:29 +05:30
Sagar Sharma
3908b510bd Merge pull request #34276 from frappe/mergify/bp/version-13-hotfix/pr-34273
fix: `rejected_serial_no` not getting copied from PR to PR(Return) (backport #34273)
2023-03-04 15:35:00 +05:30
s-aga-r
14547d94b3 chore: conflicts 2023-03-04 15:04:50 +05:30
s-aga-r
9bea2fcdfc fix: Serial No is mandatory even if the qty is 0
(cherry picked from commit cb0b6de4b9)
2023-03-02 07:08:14 +00:00
s-aga-r
bb55210f49 fix: rejected_serial_no not getting copied from PR to PR(Return)
(cherry picked from commit a9f0a11ce6)

# Conflicts:
#	erpnext/controllers/sales_and_purchase_return.py
2023-03-02 07:08:14 +00:00
Frappe PR Bot
178be42369 chore(release): Bumped to Version 13.49.1
## [13.49.1](https://github.com/frappe/erpnext/compare/v13.49.0...v13.49.1) (2023-03-01)

### Bug Fixes

* Wrap unexpectedly long text in remark ([e694550](e6945508f1))
2023-03-01 10:55:31 +00:00
Suraj Shetty
b4e775b264 Merge pull request #34264 from frappe/mergify/bp/version-13/pr-34262
fix(General Ledger): Wrap unexpectedly long word  (backport #34262)
2023-03-01 16:23:56 +05:30
Suraj Shetty
e6945508f1 fix: Wrap unexpectedly long text in remark
(cherry picked from commit ba66a6714c)
2023-03-01 10:53:42 +00:00
Suraj Shetty
5354169f31 Merge pull request #34262 from frappe/fix-general-ledger-report 2023-03-01 16:22:28 +05:30
Suraj Shetty
ba66a6714c fix: Wrap unexpectedly long text in remark 2023-03-01 16:16:58 +05:30
Frappe PR Bot
573cd3c33b chore(release): Bumped to Version 13.49.0
# [13.49.0](https://github.com/frappe/erpnext/compare/v13.48.1...v13.49.0) (2023-02-28)

### Bug Fixes

* conversion factor not set ([59d5797](59d579764d))
* german translations ([#31732](https://github.com/frappe/erpnext/issues/31732)) ([d44da6c](d44da6c820))
* ignore remaining leaves calculation for cf leaves after expiry ([d82ba4e](d82ba4e86f))
* incorrect acc depr amount if multiple FBs with straight line or manual method ([304e6bb](304e6bb996))
* incorrect color in the BOM Stock Report ([e98b346](e98b34617f))
* incorrect leave balance after carry-forwarded leave expiry ([a3a9cd5](a3a9cd5174))
* manual depr schedule ([7176799](71767994a7))
* multiple pos conversion issue resolved ([de631e6](de631e65cc))
* not able to repost gl entries ([2039bd0](2039bd066d))
* **patch:** create only 80G custom fields instead of running the whole setup ([#34183](https://github.com/frappe/erpnext/issues/34183)) ([806f7e5](806f7e5eef))
* permission error while calling get_work_order_items ([3d7b2b1](3d7b2b1a6d))
* pos return throwing amount greater than grand total ([f6607a6](f6607a6050))
* Remove missing DocField in fetch_from ([45645c1](45645c1064))
* set `from_warehouse` and `to_warehouse` while mapping SE ([b1ecca3](b1ecca3a16))
* **test:** use standalone method to fetch work orders from SO ([7971c14](7971c149ed))
* ui freeze on item selection in sales invoice ([d1b611d](d1b611d37f))
* user shouldn't able to make item price for item template ([69f1247](69f1247fab))
* zero division error while making LCV ([91a95ad](91a95adcb6))

### Features

* provision to convert transaction based reposting to item warehouse based reposting ([59c6eb5](59c6eb591b))

### Performance Improvements

* fetch SLE's on demand and memoize ([642692a](642692a040))
2023-02-28 13:29:25 +00:00
ruthra kumar
b6edadb3cb Merge pull request #34239 from frappe/version-13-hotfix
chore: release v13
2023-02-28 18:57:46 +05:30
ruthra kumar
d65df443fc Merge pull request #34246 from frappe/mergify/bp/version-13-hotfix/pr-34241
fix: pos return throwing amount greater than grand total (backport #34241)
2023-02-28 18:33:47 +05:30
ruthra kumar
f6607a6050 fix: pos return throwing amount greater than grand total
(cherry picked from commit 35c70f39fa)
2023-02-28 12:53:30 +00:00
Sagar Sharma
75d98ef205 Merge pull request #34237 from frappe/mergify/bp/version-13-hotfix/pr-34060
fix: multiple Point of Sale conversion issue resolved (backport #34060)
2023-02-28 16:49:46 +05:30
Vishal
fd1d2cd203 chore: minor changes in pos_controller
(cherry picked from commit f18ae5856f)
2023-02-28 09:26:37 +00:00
Vishal
c66dc5658f chore: minor change
(cherry picked from commit a51bec0269)
2023-02-28 09:26:36 +00:00
Vishal
1ebf2dd2bf chore: minor changes added to code
(cherry picked from commit 3ebe7d861d)
2023-02-28 09:26:36 +00:00
Vishal
de631e65cc fix: multiple pos conversion issue resolved
(cherry picked from commit 1de531e56e)
2023-02-28 09:26:36 +00:00
ruthra kumar
02f2844db2 Merge pull request #34218 from frappe/mergify/bp/version-13-hotfix/pr-34207
fix: permission error while calling get_work_order_items (backport #34207)
2023-02-28 11:04:52 +05:30
Frappe PR Bot
c4d9576f9f chore(release): Bumped to Version 13.48.1
## [13.48.1](https://github.com/frappe/erpnext/compare/v13.48.0...v13.48.1) (2023-02-27)

### Bug Fixes

* not able to repost gl entries ([a34aff6](a34aff6f49))
2023-02-27 14:34:43 +00:00
rohitwaghchaure
74303b65cf Merge pull request #34228 from frappe/mergify/bp/version-13/pr-34209
fix: not able to repost gl entries (backport #34206) (backport #34209)
2023-02-27 20:03:15 +05:30
Rohit Waghchaure
a34aff6f49 fix: not able to repost gl entries
(cherry picked from commit 7d10dd9ea8)
(cherry picked from commit 2039bd066d)
2023-02-27 14:08:29 +00:00
Sagar Sharma
f105c1bd5e Merge pull request #34227 from frappe/mergify/bp/version-13-hotfix/pr-34225
fix: set `from_warehouse` and `to_warehouse` while mapping SE (backport #34225)
2023-02-27 17:56:08 +05:30
Sagar Sharma
264c314416 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-34225 2023-02-27 17:32:34 +05:30
Sagar Sharma
a71a336e59 chore: conflicts 2023-02-27 17:32:05 +05:30
Anand Baburajan
7db3645298 Merge pull request #34215 from frappe/mergify/bp/version-13-hotfix/pr-34205
fix: asset manual depr schedule (backport #34205)
2023-02-27 13:31:44 +05:30
s-aga-r
b1ecca3a16 fix: set from_warehouse and to_warehouse while mapping SE
(cherry picked from commit c09a61f360)

# Conflicts:
#	erpnext/stock/doctype/material_request/material_request.py
2023-02-27 07:23:20 +00:00
Anand Baburajan
cbfa188d3d Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-34205 2023-02-27 12:41:45 +05:30
Sagar Sharma
4f7344c278 Merge pull request #34224 from frappe/mergify/bp/version-13-hotfix/pr-34212
fix: Remove missing DocField in fetch_from (backport #34212)
2023-02-27 12:28:55 +05:30
Brian Pond
45645c1064 fix: Remove missing DocField in fetch_from
(cherry picked from commit 83f3e317e1)
2023-02-27 06:24:23 +00:00
mergify[bot]
d44da6c820 fix: german translations (#31732)
fix: german translations (#31732)

(cherry picked from commit 6b510546ae)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-02-27 11:50:12 +05:30
Sagar Sharma
95ea28f14d Merge pull request #34209 from frappe/mergify/bp/version-13-hotfix/pr-34206
fix: not able to repost gl entries (backport #34206)
2023-02-27 10:26:38 +05:30
ruthra kumar
7971c149ed fix(test): use standalone method to fetch work orders from SO
(cherry picked from commit a11d3327df)
2023-02-27 10:14:36 +05:30
ruthra kumar
3d7b2b1a6d fix: permission error while calling get_work_order_items
(cherry picked from commit b6bad728cd)
2023-02-27 10:14:31 +05:30
anandbaburajan
9942a9d40a chore: refactor long if conditions
(cherry picked from commit d56ca011fe)
2023-02-26 14:38:43 +00:00
anandbaburajan
9a607b9bd0 chore: should prepare schedule if not draft
(cherry picked from commit 75386e3653)
2023-02-26 14:38:42 +00:00
anandbaburajan
304e6bb996 fix: incorrect acc depr amount if multiple FBs with straight line or manual method
(cherry picked from commit dda6baea3e)
2023-02-26 14:38:42 +00:00
anandbaburajan
4a557b47d7 chore: handle change in opening_accumulated_depreciation properly
(cherry picked from commit b0d670a51d)
2023-02-26 14:38:42 +00:00
anandbaburajan
71767994a7 fix: manual depr schedule
(cherry picked from commit 971c0720e5)
2023-02-26 14:38:41 +00:00
Rohit Waghchaure
2039bd066d fix: not able to repost gl entries
(cherry picked from commit 7d10dd9ea8)
2023-02-24 15:41:22 +00:00
rohitwaghchaure
b599b93ae8 Merge pull request #34201 from frappe/mergify/bp/version-13-hotfix/pr-34199
fix: conversion factor not set (backport #34199)
2023-02-24 17:52:40 +05:30
Rohit Waghchaure
59d579764d fix: conversion factor not set
(cherry picked from commit 8e46aebc50)
2023-02-24 09:28:37 +00:00
Frappe PR Bot
5f25cea322 chore(release): Bumped to Version 13.48.0
# [13.48.0](https://github.com/frappe/erpnext/compare/v13.47.0...v13.48.0) (2023-02-24)

### Bug Fixes

* incorrect color in the BOM Stock Report ([0490e3b](0490e3bfe6))

### Features

* provision to convert transaction based reposting to item warehouse based reposting ([c8ec365](c8ec365594))
2023-02-24 09:08:54 +00:00
rohitwaghchaure
6a0c24e7b3 Merge pull request #34196 from frappe/mergify/bp/version-13/pr-34178
fix: incorrect color in the BOM Stock Report (backport #34173) (backport #34178)
2023-02-24 14:37:28 +05:30
rohitwaghchaure
8eb6053c97 Merge pull request #34198 from frappe/mergify/bp/version-13/pr-34197
feat: provision to convert transaction based reposting to item wareho… (backport #34115) (backport #34197)
2023-02-24 14:37:14 +05:30
Rohit Waghchaure
c8ec365594 feat: provision to convert transaction based reposting to item warehouse based reposting
(cherry picked from commit f1383b5ef9)
(cherry picked from commit 59c6eb591b)
2023-02-24 06:27:41 +00:00
rohitwaghchaure
0fbd29b16d Merge pull request #34197 from frappe/mergify/bp/version-13-hotfix/pr-34115
feat: provision to convert transaction based reposting to item wareho… (backport #34115)
2023-02-24 11:54:56 +05:30
Rucha Mahabal
806f7e5eef fix(patch): create only 80G custom fields instead of running the whole setup (#34183) 2023-02-24 11:32:46 +05:30
Rohit Waghchaure
59c6eb591b feat: provision to convert transaction based reposting to item warehouse based reposting
(cherry picked from commit f1383b5ef9)
2023-02-24 05:51:56 +00:00
Rohit Waghchaure
0490e3bfe6 fix: incorrect color in the BOM Stock Report
(cherry picked from commit a8f03ebf7f)
(cherry picked from commit e98b34617f)
2023-02-24 05:40:48 +00:00
rohitwaghchaure
0aeef34944 Merge pull request #34191 from frappe/mergify/bp/version-13-hotfix/pr-34189
fix: user shouldn't able to make item price for item template (backport #34189)
2023-02-24 09:23:41 +05:30
Rohit Waghchaure
69f1247fab fix: user shouldn't able to make item price for item template
(cherry picked from commit 6417ae0ee8)
2023-02-23 15:18:42 +00:00
rohitwaghchaure
2d01b72b04 Merge pull request #34178 from frappe/mergify/bp/version-13-hotfix/pr-34173
fix: incorrect color in the BOM Stock Report (backport #34173)
2023-02-23 20:47:07 +05:30
ruthra kumar
bb4c968d95 Merge pull request #34185 from frappe/mergify/bp/version-13-hotfix/pr-34022
perf: Gross Profit report will fetch SLE's on demand and memoize (backport #34022)
2023-02-23 12:54:47 +05:30
ruthra kumar
c40aa580c5 refactor: use docstatus from Delivery Note Item
(cherry picked from commit 88d888d9d0)
2023-02-23 06:26:15 +00:00
ruthra kumar
642692a040 perf: fetch SLE's on demand and memoize
(cherry picked from commit 3e5691072a)
2023-02-23 06:26:15 +00:00
rohitwaghchaure
fd24d52d86 Merge pull request #34182 from frappe/mergify/bp/version-13-hotfix/pr-34172
fix: zero division error while making LCV (backport #34172)
2023-02-23 11:25:24 +05:30
Rohit Waghchaure
91a95adcb6 fix: zero division error while making LCV
(cherry picked from commit 80e94a08cf)
2023-02-23 05:24:56 +00:00
ruthra kumar
fe04b5a2b9 Merge pull request #34179 from frappe/mergify/bp/version-13-hotfix/pr-34176
fix: ui freeze upon item selection in sales invoice (backport #34176)
2023-02-23 10:52:12 +05:30
ruthra kumar
d1b611d37f fix: ui freeze on item selection in sales invoice
(cherry picked from commit 6412583e98)
2023-02-23 05:06:50 +00:00
Rohit Waghchaure
e98b34617f fix: incorrect color in the BOM Stock Report
(cherry picked from commit a8f03ebf7f)
2023-02-23 04:25:45 +00:00
Rucha Mahabal
6391ccd56a Merge pull request #34175 from ruchamahabal/fix-leave-balances 2023-02-22 20:17:13 +05:30
Rucha Mahabal
b848b77815 test: leave details with expired cf leaves 2023-02-22 19:46:08 +05:30
Rucha Mahabal
d82ba4e86f fix: ignore remaining leaves calculation for cf leaves after expiry
- calculate correct cf expiry in the entire allocation period
2023-02-22 19:44:23 +05:30
Rucha Mahabal
aea9d82672 test: leaves allocated before and after cf leave expiry is same 2023-02-22 19:44:06 +05:30
Rucha Mahabal
a3a9cd5174 fix: incorrect leave balance after carry-forwarded leave expiry 2023-02-22 19:43:56 +05:30
138 changed files with 3688 additions and 1800 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@
"determine_address_tax_category_from",
"column_break_19",
"add_taxes_from_item_tax_template",
"book_tax_discount_loss",
"period_closing_settings_section",
"acc_frozen_upto",
"frozen_accounts_modifier",
@@ -174,6 +175,7 @@
},
{
"default": "0",
"description": "Payment Terms from orders will be fetched into the invoices as is",
"fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check",
"label": "Automatically Fetch Payment Terms from Order"
@@ -284,6 +286,13 @@
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
"fieldtype": "Check",
"label": "Allow multi-currency invoices against single party account"
},
{
"default": "0",
"description": "Split Early Payment Discount Loss into Income and Tax Loss",
"fieldname": "book_tax_discount_loss",
"fieldtype": "Check",
"label": "Book Tax Loss on Early Payment Discount"
}
],
"icon": "icon-cog",
@@ -291,7 +300,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-07-11 13:37:50.605141",
"modified": "2023-04-14 17:22:03.680886",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

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

View File

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

View File

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

View File

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

View File

@@ -256,8 +256,6 @@ frappe.ui.form.on('Payment Entry', {
frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
party_account_currency, "references");
frm.set_currency_labels(["amount"], company_currency, "deductions");
cur_frm.set_df_property("source_exchange_rate", "description",
("1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency));

View File

@@ -7,7 +7,7 @@ from functools import reduce
import frappe
from frappe import ValidationError, _, scrub, throw
from frappe.utils import cint, comma_or, flt, getdate, nowdate
from frappe.utils import cint, comma_or, flt, get_link_to_form, getdate, nowdate
from six import iteritems, string_types
import erpnext
@@ -168,8 +168,31 @@ class PaymentEntry(AccountsController):
for reference in self.references:
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
doc = frappe.get_doc(reference.reference_doctype, reference.reference_name)
repost_required = False
for adv_reference in doc.get("advances"):
if adv_reference.exchange_gain_loss != 0:
repost_required = True
break
if repost_required:
for item in doc.get("items"):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
frappe.msgprint(
_(
"Linked Invoice {0} has Exchange Gain/Loss GL entries due to this Payment. Submit a Journal manually to reverse its effects."
).format(get_link_to_form(doc.doctype, doc.name))
)
repost_required = False
doc.delink_advance_entries(self.name)
if repost_required:
doc.reload()
doc.docstatus = 2
doc.make_gl_entries()
doc.docstatus = 1
doc.make_gl_entries()
def set_missing_values(self):
if self.payment_type == "Internal Transfer":
for field in (
@@ -417,7 +440,7 @@ class PaymentEntry(AccountsController):
for ref in self.get("references"):
if ref.payment_term and ref.reference_name:
key = (ref.payment_term, ref.reference_name)
key = (ref.payment_term, ref.reference_name, ref.reference_doctype)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += ref.allocated_amount
@@ -425,20 +448,37 @@ class PaymentEntry(AccountsController):
payment_schedule = frappe.get_all(
"Payment Schedule",
filters={"parent": ref.reference_name},
fields=["paid_amount", "payment_amount", "payment_term", "discount", "outstanding"],
fields=[
"paid_amount",
"payment_amount",
"payment_term",
"discount",
"outstanding",
"discount_type",
],
)
for term in payment_schedule:
invoice_key = (term.payment_term, ref.reference_name)
invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
term.discount / 100
)
if not (term.discount_type and term.discount):
continue
if term.discount_type == "Percentage":
invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
term.discount / 100
)
else:
invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount
for idx, (key, allocated_amount) in enumerate(iteritems(invoice_payment_amount_map), 1):
if not invoice_paid_amount_map.get(key):
frappe.throw(_("Payment term {0} not used in {1}").format(key[0], key[1]))
allocated_amount = self.get_allocated_amount_in_transaction_currency(
allocated_amount, key[2], key[1]
)
outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))
@@ -473,6 +513,33 @@ class PaymentEntry(AccountsController):
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
)
def get_allocated_amount_in_transaction_currency(
self, allocated_amount, reference_doctype, reference_docname
):
"""
Payment Entry could be in base currency while reference's payment schedule
is always in transaction currency.
E.g.
* SI with base=INR and currency=USD
* SI with payment schedule in USD
* PE in INR (accounting done in base currency)
"""
ref_currency, ref_exchange_rate = frappe.db.get_value(
reference_doctype, reference_docname, ["currency", "conversion_rate"]
)
is_single_currency = self.paid_from_account_currency == self.paid_to_account_currency
# PE in different currency
reference_is_multi_currency = self.paid_from_account_currency != ref_currency
if not (is_single_currency and reference_is_multi_currency):
return allocated_amount
allocated_amount = flt(
allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount")
)
return allocated_amount
def set_status(self):
if self.docstatus == 2:
self.status = "Cancelled"
@@ -1778,10 +1845,20 @@ def get_bill_no_and_update_amounts(
@frappe.whitelist()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
def get_payment_entry(
dt,
dn,
party_amount=None,
bank_account=None,
bank_amount=None,
reference_date=None,
):
reference_doc = None
doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (
100.0 + over_billing_allowance
):
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
party_type = set_party_type(dt)
@@ -1799,8 +1876,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
)
paid_amount, received_amount, discount_amount = apply_early_payment_discount(
paid_amount, received_amount, doc
reference_date = getdate(reference_date)
paid_amount, received_amount, discount_amount, valid_discounts = apply_early_payment_discount(
paid_amount, received_amount, doc, party_account_currency, reference_date
)
pe = frappe.new_doc("Payment Entry")
@@ -1808,6 +1886,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.company = doc.company
pe.cost_center = doc.get("cost_center")
pe.posting_date = nowdate()
pe.reference_date = reference_date
pe.mode_of_payment = doc.get("mode_of_payment")
pe.party_type = party_type
pe.party = doc.get(scrub(party_type))
@@ -1841,14 +1920,19 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
frappe.msgprint(_("{0} is on hold till {1}").format(doc.name, doc.release_date))
else:
if doc.doctype in ("Sales Invoice", "Purchase Invoice") and frappe.get_value(
if doc.doctype in (
"Sales Invoice",
"Purchase Invoice",
"Purchase Order",
"Sales Order",
) and frappe.get_value(
"Payment Terms Template",
{"name": doc.payment_terms_template},
"allocate_payment_based_on_payment_terms",
):
for reference in get_reference_as_per_payment_terms(
doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount
doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
):
pe.append("references", reference)
else:
@@ -1899,16 +1983,17 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
reference_doc = doc
pe.set_exchange_rate(ref_doc=reference_doc)
pe.set_amounts()
if discount_amount:
pe.set_gain_or_loss(
account_details={
"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
"cost_center": pe.cost_center
or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": discount_amount * (-1 if payment_type == "Pay" else 1),
}
base_total_discount_loss = 0
if frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss"):
base_total_discount_loss = split_early_payment_discount_loss(pe, doc, valid_discounts)
set_pending_discount_loss(
pe, doc, discount_amount, base_total_discount_loss, party_account_currency
)
pe.set_difference_amount()
pe.set_difference_amount()
return pe
@@ -2044,20 +2129,30 @@ def set_paid_amount_and_received_amount(
return paid_amount, received_amount
def apply_early_payment_discount(paid_amount, received_amount, doc):
def apply_early_payment_discount(
paid_amount, received_amount, doc, party_account_currency, reference_date
):
total_discount = 0
valid_discounts = []
eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
has_payment_schedule = hasattr(doc, "payment_schedule") and doc.payment_schedule
if doc.doctype in eligible_for_payments and has_payment_schedule:
# Non eligible documents may not have `company_currency` field
is_multi_currency = party_account_currency != doc.company_currency
for term in doc.payment_schedule:
if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
if not term.discounted_amount and term.discount and reference_date <= term.discount_date:
if term.discount_type == "Percentage":
discount_amount = flt(doc.get("grand_total")) * (term.discount / 100)
grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
discount_amount = flt(grand_total) * (term.discount / 100)
else:
discount_amount = term.discount
discount_amount_in_foreign_currency = discount_amount * doc.get("conversion_rate", 1)
# if accounting is done in the same currency, paid_amount = received_amount
conversion_rate = doc.get("conversion_rate", 1) if is_multi_currency else 1
discount_amount_in_foreign_currency = discount_amount * conversion_rate
if doc.doctype == "Sales Invoice":
paid_amount -= discount_amount
@@ -2066,23 +2161,151 @@ def apply_early_payment_discount(paid_amount, received_amount, doc):
received_amount -= discount_amount
paid_amount -= discount_amount_in_foreign_currency
valid_discounts.append({"type": term.discount_type, "discount": term.discount})
total_discount += discount_amount
if total_discount:
money = frappe.utils.fmt_money(total_discount, currency=doc.get("currency"))
currency = doc.get("currency") if is_multi_currency else doc.company_currency
money = frappe.utils.fmt_money(total_discount, currency=currency)
frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
return paid_amount, received_amount, total_discount
return paid_amount, received_amount, total_discount, valid_discounts
def set_pending_discount_loss(
pe, doc, discount_amount, base_total_discount_loss, party_account_currency
):
# If multi-currency, get base discount amount to adjust with base currency deductions/losses
if party_account_currency != doc.company_currency:
discount_amount = discount_amount * doc.get("conversion_rate", 1)
# Avoid considering miniscule losses
discount_amount = flt(discount_amount - base_total_discount_loss, doc.precision("grand_total"))
# Set base discount amount (discount loss/pending rounding loss) in deductions
if discount_amount > 0.0:
positive_negative = -1 if pe.payment_type == "Pay" else 1
# If tax loss booking is enabled, pending loss will be rounding loss.
# Otherwise it will be the total discount loss.
book_tax_loss = frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss")
account_type = "round_off_account" if book_tax_loss else "default_discount_account"
pe.set_gain_or_loss(
account_details={
"account": frappe.get_cached_value("Company", pe.company, account_type),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": discount_amount * positive_negative,
}
)
def split_early_payment_discount_loss(pe, doc, valid_discounts) -> float:
"""Split early payment discount into Income Loss & Tax Loss."""
total_discount_percent = get_total_discount_percent(doc, valid_discounts)
if not total_discount_percent:
return 0.0
base_loss_on_income = add_income_discount_loss(pe, doc, total_discount_percent)
base_loss_on_taxes = add_tax_discount_loss(pe, doc, total_discount_percent)
# Round off total loss rather than individual losses to reduce rounding error
return flt(base_loss_on_income + base_loss_on_taxes, doc.precision("grand_total"))
def get_total_discount_percent(doc, valid_discounts) -> float:
"""Get total percentage and amount discount applied as a percentage."""
total_discount_percent = (
sum(
discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage"
)
or 0.0
)
# Operate in percentages only as it makes the income & tax split easier
total_discount_amount = (
sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Amount")
or 0.0
)
if total_discount_amount:
discount_percentage = (total_discount_amount / doc.get("grand_total")) * 100
total_discount_percent += discount_percentage
return total_discount_percent
return total_discount_percent
def add_income_discount_loss(pe, doc, total_discount_percent) -> float:
"""Add loss on income discount in base currency."""
precision = doc.precision("total")
base_loss_on_income = doc.get("base_total") * (total_discount_percent / 100)
pe.append(
"deductions",
{
"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(base_loss_on_income, precision),
},
)
return base_loss_on_income # Return loss without rounding
def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
"""Add loss on tax discount in base currency."""
tax_discount_loss = {}
base_total_tax_loss = 0
precision = doc.precision("tax_amount_after_discount_amount", "taxes")
# The same account head could be used more than once
for tax in doc.get("taxes", []):
base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * (
total_discount_percentage / 100
)
account = tax.get("account_head")
if not tax_discount_loss.get(account):
tax_discount_loss[account] = base_tax_loss
else:
tax_discount_loss[account] += base_tax_loss
for account, loss in tax_discount_loss.items():
base_total_tax_loss += loss
if loss == 0.0:
continue
pe.append(
"deductions",
{
"account": account,
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(loss, precision),
},
)
return base_total_tax_loss # Return loss without rounding
def get_reference_as_per_payment_terms(
payment_schedule, dt, dn, doc, grand_total, outstanding_amount
payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
):
references = []
is_multi_currency_acc = (doc.currency != doc.company_currency) and (
party_account_currency != doc.company_currency
)
for payment_term in payment_schedule:
payment_term_outstanding = flt(
payment_term.payment_amount - payment_term.paid_amount, payment_term.precision("payment_amount")
)
if not is_multi_currency_acc:
# If accounting is done in company currency for multi-currency transaction
payment_term_outstanding = flt(
payment_term_outstanding * doc.get("conversion_rate"), payment_term.precision("payment_amount")
)
if payment_term_outstanding:
references.append(

View File

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

View File

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

View File

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

View File

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

View File

@@ -161,7 +161,7 @@ class POSInvoice(SalesInvoice):
bold_item_name = frappe.bold(item.item_name)
bold_extra_batch_qty_needed = frappe.bold(
abs(available_batch_qty - reserved_batch_qty - item.qty)
abs(available_batch_qty - reserved_batch_qty - item.stock_qty)
)
bold_invalid_batch_no = frappe.bold(item.batch_no)
@@ -172,7 +172,7 @@ class POSInvoice(SalesInvoice):
).format(item.idx, bold_invalid_batch_no, bold_item_name),
title=_("Item Unavailable"),
)
elif (available_batch_qty - reserved_batch_qty - item.qty) < 0:
elif (available_batch_qty - reserved_batch_qty - item.stock_qty) < 0:
frappe.throw(
_(
"Row #{}: Batch No. {} of item {} has less than required stock available, {} more required"
@@ -246,7 +246,7 @@ class POSInvoice(SalesInvoice):
),
title=_("Item Unavailable"),
)
elif is_stock_item and flt(available_stock) < flt(d.qty):
elif is_stock_item and flt(available_stock) < flt(d.stock_qty):
frappe.throw(
_(
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
@@ -652,7 +652,7 @@ def get_bundle_availability(bundle_item_code, warehouse):
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
available_qty = item_bin_qty - item_pos_reserved_qty
max_available_bundles = available_qty / item.qty
max_available_bundles = available_qty / item.stock_qty
if bundle_bin_qty > max_available_bundles and frappe.get_value(
"Item", item.item_code, "is_stock_item"
):

View File

@@ -15,7 +15,7 @@
</div>
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
<div>
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party[0] }}</b></h5>
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party_name[0] }}</b></h5>
<h5 style="float: right;">
{{ _("Date: ") }}
<b>{{ frappe.format(filters.from_date, 'Date')}}

View File

@@ -24,7 +24,7 @@ from erpnext.accounts.report.general_ledger.general_ledger import execute as get
class ProcessStatementOfAccounts(Document):
def validate(self):
if not self.subject:
self.subject = "Statement Of Accounts for {{ customer.name }}"
self.subject = "Statement Of Accounts for {{ customer.customer_name }}"
if not self.body:
self.body = "Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}."
@@ -87,6 +87,7 @@ def get_report_pdf(doc, consolidated=True):
"account": [doc.account] if doc.account else None,
"party_type": "Customer",
"party": [entry.customer],
"party_name": [entry.customer_name] if entry.customer_name else None,
"presentation_currency": presentation_currency,
"group_by": doc.group_by,
"currency": doc.currency,
@@ -155,7 +156,7 @@ def get_customers_based_on_territory_or_customer_group(customer_collection, coll
]
return frappe.get_list(
"Customer",
fields=["name", "email_id"],
fields=["name", "customer_name", "email_id"],
filters=[[fields_dict[customer_collection], "IN", selected]],
)
@@ -178,7 +179,7 @@ def get_customers_based_on_sales_person(sales_person):
if sales_person_records.get("Customer"):
return frappe.get_list(
"Customer",
fields=["name", "email_id"],
fields=["name", "customer_name", "email_id"],
filters=[["name", "in", list(sales_person_records["Customer"])]],
)
else:
@@ -227,7 +228,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
if customer_collection == "Sales Partner":
customers = frappe.get_list(
"Customer",
fields=["name", "email_id"],
fields=["name", "customer_name", "email_id"],
filters=[["default_sales_partner", "=", collection_name]],
)
else:
@@ -244,7 +245,12 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
continue
customer_list.append(
{"name": customer.name, "primary_email": primary_email, "billing_email": billing_email}
{
"name": customer.name,
"customer_name": customer.customer_name,
"primary_email": primary_email,
"billing_email": billing_email,
}
)
return customer_list

View File

@@ -1,12 +1,12 @@
{
"actions": [],
"allow_workflow": 1,
"creation": "2020-08-03 16:35:21.852178",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"customer",
"customer_name",
"billing_email",
"primary_email"
],
@@ -30,11 +30,18 @@
"fieldtype": "Read Only",
"in_list_view": 1,
"label": "Billing Email"
},
{
"fetch_from": "customer.customer_name",
"fieldname": "customer_name",
"fieldtype": "Data",
"label": "Customer Name",
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-08-03 22:55:38.875601",
"modified": "2023-03-13 00:12:34.508086",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts Customer",
@@ -43,5 +50,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

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

View File

@@ -75,9 +75,12 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
if (doc.docstatus == 1 && doc.outstanding_amount!=0
&& !(cint(doc.is_return) && doc.return_against)) {
cur_frm.add_custom_button(__('Payment'),
this.make_payment_entry, __('Create'));
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
this.frm.add_custom_button(
__('Payment'),
() => this.make_payment_entry(),
__('Create')
);
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
}
if(doc.docstatus==1 && !doc.is_return) {
@@ -313,6 +316,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
make_inter_company_invoice: function() {
let me = this;
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_inter_company_purchase_invoice",
frm: me.frm
@@ -1078,7 +1082,7 @@ var select_loyalty_program = function(frm, loyalty_programs) {
]
});
dialog.set_primary_action(__("Set"), function() {
dialog.set_primary_action(__("Set Loyalty Program"), function() {
dialog.hide();
return frappe.call({
method: "frappe.client.set_value",

View File

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

View File

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

View File

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

View File

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

View File

@@ -138,7 +138,8 @@ def prepare_companywise_opening_balance(asset_data, liability_data, equity_data,
for data in [asset_data, liability_data, equity_data]:
if data:
account_name = get_root_account_name(data[0].root_type, company)
opening_value += get_opening_balance(account_name, data, company) or 0.0
if account_name:
opening_value += get_opening_balance(account_name, data, company) or 0.0
opening_balance[company] = opening_value
@@ -155,7 +156,7 @@ def get_opening_balance(account_name, data, company):
def get_root_account_name(root_type, company):
return frappe.get_all(
root_account = frappe.get_all(
"Account",
fields=["account_name"],
filters={
@@ -165,7 +166,10 @@ def get_root_account_name(root_type, company):
"parent_account": ("is", "not set"),
},
as_list=1,
)[0][0]
)
if root_account:
return root_account[0][0]
def get_profit_loss_data(fiscal_year, companies, columns, filters):

View File

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

View File

@@ -38,8 +38,11 @@
{% if(data[i].posting_date) { %}
<td>{%= frappe.datetime.str_to_user(data[i].posting_date) %}</td>
<td>{%= data[i].voucher_type %}
<br>{%= data[i].voucher_no %}</td>
<td>
<br>{%= data[i].voucher_no %}
</td>
{% var longest_word = cstr(data[i].remarks).split(" ").reduce((longest, word) => word.length > longest.length ? word : longest, ""); %}
<td {% if longest_word.length > 45 %} class="overflow-wrap-anywhere" {% endif %}>
<span>
{% if(!(filters.party || filters.account)) { %}
{%= data[i].party || data[i].account %}
<br>
@@ -49,11 +52,14 @@
{% if(data[i].bill_no) { %}
<br>{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}
{% } %}
</td>
<td style="text-align: right">
{%= format_currency(data[i].debit, filters.presentation_currency) %}</td>
<td style="text-align: right">
{%= format_currency(data[i].credit, filters.presentation_currency) %}</td>
</span>
</td>
<td style="text-align: right">
{%= format_currency(data[i].debit, filters.presentation_currency) %}
</td>
<td style="text-align: right">
{%= format_currency(data[i].credit, filters.presentation_currency) %}
</td>
{% } else { %}
<td></td>
<td></td>

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ from frappe.query_builder import Order
from frappe.utils import cint, flt
from erpnext.controllers.queries import get_match_cond
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
from erpnext.stock.utils import get_incoming_rate
@@ -364,6 +365,7 @@ def get_column_names():
class GrossProfitGenerator(object):
def __init__(self, filters=None):
self.sle = {}
self.data = []
self.average_buying_rate = {}
self.filters = frappe._dict(filters)
@@ -373,7 +375,6 @@ class GrossProfitGenerator(object):
if filters.group_by == "Invoice":
self.group_items_by_invoice()
self.load_stock_ledger_entries()
self.load_product_bundle()
self.load_non_stock_items()
self.get_returned_invoice_items()
@@ -465,7 +466,14 @@ class GrossProfitGenerator(object):
):
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
for returned_item_row in returned_item_rows:
row.qty += flt(returned_item_row.qty)
# returned_items 'qty' should be stateful
if returned_item_row.qty != 0:
if row.qty >= abs(returned_item_row.qty):
row.qty += returned_item_row.qty
returned_item_row.qty = 0
else:
row.qty = 0
returned_item_row.qty += row.qty
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
if flt(row.qty) or row.base_amount:
@@ -563,7 +571,7 @@ class GrossProfitGenerator(object):
return flt(row.qty) * item_rate
else:
my_sle = self.sle.get((item_code, row.warehouse))
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
if (row.update_stock or row.dn_detail) and my_sle:
parenttype, parent = row.parenttype, row.parent
if row.dn_detail:
@@ -581,7 +589,7 @@ class GrossProfitGenerator(object):
dn["item_row"],
dn["warehouse"],
)
my_sle = self.sle.get((item_code, warehouse))
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, item_row, item_code
)
@@ -597,15 +605,12 @@ class GrossProfitGenerator(object):
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
from frappe.query_builder.functions import Sum
delivery_note = frappe.qb.DocType("Delivery Note")
delivery_note_item = frappe.qb.DocType("Delivery Note Item")
query = (
frappe.qb.from_(delivery_note)
.inner_join(delivery_note_item)
.on(delivery_note.name == delivery_note_item.parent)
frappe.qb.from_(delivery_note_item)
.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
.where(delivery_note.docstatus == 1)
.where(delivery_note_item.docstatus == 1)
.where(delivery_note_item.item_code == item_code)
.where(delivery_note_item.against_sales_order == sales_order)
.where(delivery_note_item.so_detail == so_detail)
@@ -667,6 +672,19 @@ class GrossProfitGenerator(object):
if self.filters.to_date:
conditions += " and posting_date <= %(to_date)s"
conditions += " and (is_return = 0 or (is_return=1 and return_against is null))"
if self.filters.item_group:
conditions += " and {0}".format(get_item_group_condition(self.filters.item_group))
if self.filters.sales_person:
conditions += """
and exists(select 1
from `tabSales Team` st
where st.parent = `tabSales Invoice`.name
and st.sales_person = %(sales_person)s)
"""
if self.filters.group_by == "Sales Person":
sales_person_cols = ", sales.sales_person, sales.allocated_amount, sales.incentives"
sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name"
@@ -840,24 +858,36 @@ class GrossProfitGenerator(object):
"Item", item_code, ["item_name", "description", "item_group", "brand"]
)
def load_stock_ledger_entries(self):
res = frappe.db.sql(
"""select item_code, voucher_type, voucher_no,
voucher_detail_no, stock_value, warehouse, actual_qty as qty
from `tabStock Ledger Entry`
where company=%(company)s and is_cancelled = 0
order by
item_code desc, warehouse desc, posting_date desc,
posting_time desc, creation desc""",
self.filters,
as_dict=True,
)
self.sle = {}
for r in res:
if (r.item_code, r.warehouse) not in self.sle:
self.sle[(r.item_code, r.warehouse)] = []
def get_stock_ledger_entries(self, item_code, warehouse):
if item_code and warehouse:
if (item_code, warehouse) not in self.sle:
sle = qb.DocType("Stock Ledger Entry")
res = (
qb.from_(sle)
.select(
sle.item_code,
sle.voucher_type,
sle.voucher_no,
sle.voucher_detail_no,
sle.stock_value,
sle.warehouse,
sle.actual_qty.as_("qty"),
)
.where(
(sle.company == self.filters.company)
& (sle.item_code == item_code)
& (sle.warehouse == warehouse)
& (sle.is_cancelled == 0)
)
.orderby(sle.item_code)
.orderby(sle.warehouse, sle.posting_date, sle.posting_time, sle.creation, order=Order.desc)
.run(as_dict=True)
)
self.sle[(r.item_code, r.warehouse)].append(r)
self.sle[(item_code, warehouse)] = res
return self.sle[(item_code, warehouse)]
return []
def load_product_bundle(self):
self.product_bundles = {}

View File

@@ -380,3 +380,82 @@ class TestGrossProfit(FrappeTestCase):
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])
def test_crnote_against_invoice_with_multiple_instances_of_same_item(self):
"""
Item Qty for Sales Invoices with multiple instances of same item go in the -ve. Ideally, the credit noteshould cancel out the invoice items.
"""
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
# Invoice with an item added twice
sinv = self.create_sales_invoice(qty=1, rate=100, posting_date=nowdate(), do_not_submit=True)
sinv.append("items", frappe.copy_doc(sinv.items[0], ignore_no_copy=False))
sinv = sinv.save().submit()
# Create Credit Note for Invoice
cr_note = make_sales_return(sinv.name)
cr_note = cr_note.save().submit()
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
columns, data = execute(filters=filters)
expected_entry = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 0.0,
"avg._selling_rate": 0.0,
"valuation_rate": 0.0,
"selling_amount": -100.0,
"buying_amount": 0.0,
"gross_profit": -100.0,
"gross_profit_%": 100.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
# Both items of Invoice should have '0' qty
self.assertEqual(len(gp_entry), 2)
self.assertDictContainsSubset(expected_entry, gp_entry[0])
self.assertDictContainsSubset(expected_entry, gp_entry[1])
def test_standalone_cr_notes(self):
"""
Standalone cr notes will be reported as usual
"""
# Make Cr Note
sinv = self.create_sales_invoice(
qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
)
sinv.is_return = 1
sinv = sinv.save().submit()
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
columns, data = execute(filters=filters)
expected_entry = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": -1.0,
"avg._selling_rate": 100.0,
"valuation_rate": 0.0,
"selling_amount": -100.0,
"buying_amount": 0.0,
"gross_profit": -100.0,
"gross_profit_%": 100.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])

View File

@@ -78,7 +78,6 @@ def validate_filters(filters):
def get_data(filters):
accounts = frappe.db.sql(
"""select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt
@@ -118,12 +117,10 @@ def get_data(filters):
ignore_closing_entries=not flt(filters.with_period_closing_entry),
)
total_row = calculate_values(
accounts, gl_entries_by_account, opening_balances, filters, company_currency
)
calculate_values(accounts, gl_entries_by_account, opening_balances)
accumulate_values_into_parents(accounts, accounts_by_name)
data = prepare_data(accounts, filters, total_row, parent_children_map, company_currency)
data = prepare_data(accounts, filters, parent_children_map, company_currency)
data = filter_out_zero_value_rows(
data, parent_children_map, show_zero_values=filters.get("show_zero_values")
)
@@ -160,12 +157,25 @@ def get_rootwise_opening_balances(filters, report_type):
if filters.project:
additional_conditions += " and project = %(project)s"
company_fb = frappe.db.get_value("Company", filters.company, "default_finance_book")
if filters.get("include_default_book_entries"):
additional_conditions += (
" AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
)
if filters.get("finance_book"):
if company_fb and cstr(filters.get("finance_book")) != cstr(company_fb):
frappe.throw(
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
else:
additional_conditions += (
" AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
)
else:
additional_conditions += " AND (finance_book in (%(company_fb)s, '') OR finance_book IS NULL)"
else:
additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
if filters.get("finance_book"):
additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
else:
additional_conditions += " AND (finance_book in ('') OR finance_book IS NULL)"
accounting_dimensions = get_accounting_dimensions(as_list=False)
@@ -177,7 +187,7 @@ def get_rootwise_opening_balances(filters, report_type):
"year_start_date": filters.year_start_date,
"project": filters.project,
"finance_book": filters.finance_book,
"company_fb": frappe.db.get_value("Company", filters.company, "default_finance_book"),
"company_fb": company_fb,
}
if accounting_dimensions:
@@ -218,7 +228,7 @@ def get_rootwise_opening_balances(filters, report_type):
return opening
def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, company_currency):
def calculate_values(accounts, gl_entries_by_account, opening_balances):
init = {
"opening_debit": 0.0,
"opening_credit": 0.0,
@@ -228,22 +238,6 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters,
"closing_credit": 0.0,
}
total_row = {
"account": "'" + _("Total") + "'",
"account_name": "'" + _("Total") + "'",
"warn_if_negative": True,
"opening_debit": 0.0,
"opening_credit": 0.0,
"debit": 0.0,
"credit": 0.0,
"closing_debit": 0.0,
"closing_credit": 0.0,
"parent_account": None,
"indent": 0,
"has_value": True,
"currency": company_currency,
}
for d in accounts:
d.update(init.copy())
@@ -261,8 +255,28 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters,
prepare_opening_closing(d)
for field in value_fields:
total_row[field] += d[field]
def calculate_total_row(accounts, company_currency):
total_row = {
"account": "'" + _("Total") + "'",
"account_name": "'" + _("Total") + "'",
"warn_if_negative": True,
"opening_debit": 0.0,
"opening_credit": 0.0,
"debit": 0.0,
"credit": 0.0,
"closing_debit": 0.0,
"closing_credit": 0.0,
"parent_account": None,
"indent": 0,
"has_value": True,
"currency": company_currency,
}
for d in accounts:
if not d.parent_account:
for field in value_fields:
total_row[field] += d[field]
return total_row
@@ -274,7 +288,7 @@ def accumulate_values_into_parents(accounts, accounts_by_name):
accounts_by_name[d.parent_account][key] += d[key]
def prepare_data(accounts, filters, total_row, parent_children_map, company_currency):
def prepare_data(accounts, filters, parent_children_map, company_currency):
data = []
for d in accounts:
@@ -305,6 +319,7 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr
row["has_value"] = has_value
data.append(row)
total_row = calculate_total_row(accounts, company_currency)
data.extend([{}, total_row])
return data

View File

@@ -296,10 +296,6 @@ frappe.ui.form.on('Asset', {
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
},
opening_accumulated_depreciation: function(frm) {
erpnext.asset.set_accumulated_depreciation(frm);
},
make_schedules_editable: function(frm) {
if (frm.doc.finance_books) {
var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
@@ -425,6 +421,9 @@ frappe.ui.form.on('Asset', {
} else {
frm.set_value('purchase_date', purchase_doc.posting_date);
}
if (!frm.doc.is_existing_asset && !frm.doc.available_for_use_date) {
frm.set_value('available_for_use_date', frm.doc.purchase_date);
}
const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code);
if (!item) {
doctype_field = frappe.scrub(doctype)
@@ -519,19 +518,23 @@ frappe.ui.form.on('Depreciation Schedule', {
},
depreciation_amount: function(frm, cdt, cdn) {
erpnext.asset.set_accumulated_depreciation(frm);
erpnext.asset.set_accumulated_depreciation(frm, locals[cdt][cdn].finance_book_id);
}
})
});
erpnext.asset.set_accumulated_depreciation = function(frm) {
if(frm.doc.depreciation_method != "Manual") return;
erpnext.asset.set_accumulated_depreciation = function(frm, finance_book_id) {
var depreciation_method = frm.doc.finance_books[Number(finance_book_id) - 1].depreciation_method;
if(depreciation_method != "Manual") return;
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
$.each(frm.doc.schedules || [], function(i, row) {
accumulated_depreciation += flt(row.depreciation_amount);
frappe.model.set_value(row.doctype, row.name,
"accumulated_depreciation_amount", accumulated_depreciation);
if (row.finance_book_id === finance_book_id) {
accumulated_depreciation += flt(row.depreciation_amount);
frappe.model.set_value(row.doctype, row.name, "accumulated_depreciation_amount", accumulated_depreciation);
};
})
};

View File

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

View File

@@ -84,14 +84,55 @@ class Asset(AccountsController):
if self.calculate_depreciation:
self.value_after_depreciation = 0
self.set_depreciation_rate()
self.make_depreciation_schedule(date_of_disposal)
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
if self.should_prepare_depreciation_schedule():
self.make_depreciation_schedule(date_of_disposal)
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
else:
self.finance_books = []
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
self.opening_accumulated_depreciation
)
def should_prepare_depreciation_schedule(self):
if not self.get("schedules"):
return True
old_asset_doc = self.get_doc_before_save()
if not old_asset_doc:
return True
have_asset_details_been_modified = (
old_asset_doc.gross_purchase_amount != self.gross_purchase_amount
or old_asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation
or old_asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked
)
if have_asset_details_been_modified:
return True
manual_fb_idx = -1
for d in self.finance_books:
if d.depreciation_method == "Manual":
manual_fb_idx = d.idx - 1
no_manual_depr_or_have_manual_depr_details_been_modified = (
manual_fb_idx == -1
or old_asset_doc.finance_books[manual_fb_idx].total_number_of_depreciations
!= self.finance_books[manual_fb_idx].total_number_of_depreciations
or old_asset_doc.finance_books[manual_fb_idx].frequency_of_depreciation
!= self.finance_books[manual_fb_idx].frequency_of_depreciation
or old_asset_doc.finance_books[manual_fb_idx].depreciation_start_date
!= getdate(self.finance_books[manual_fb_idx].depreciation_start_date)
or old_asset_doc.finance_books[manual_fb_idx].expected_value_after_useful_life
!= self.finance_books[manual_fb_idx].expected_value_after_useful_life
)
if no_manual_depr_or_have_manual_depr_details_been_modified:
return True
return False
def validate_item(self):
item = frappe.get_cached_value(
"Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1
@@ -225,9 +266,7 @@ class Asset(AccountsController):
)
def make_depreciation_schedule(self, date_of_disposal):
if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get(
"schedules"
):
if not self.get("schedules"):
self.schedules = []
if not self.available_for_use_date:
@@ -257,17 +296,42 @@ class Asset(AccountsController):
if has_pro_rata:
number_of_pending_depreciations += 1
has_wdv_or_dd_non_yearly_pro_rata = False
if (
finance_book.depreciation_method in ("Written Down Value", "Double Declining Balance")
and cint(finance_book.frequency_of_depreciation) != 12
):
has_wdv_or_dd_non_yearly_pro_rata = self.check_is_pro_rata(
finance_book, wdv_or_dd_non_yearly=True
)
skip_row = False
should_get_last_day = is_last_day_of_the_month(finance_book.depreciation_start_date)
depreciation_amount = 0
for n in range(start[finance_book.idx - 1], number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
if n > 0 and len(self.get("schedules")) > n - 1:
prev_depreciation_amount = self.get("schedules")[n - 1].depreciation_amount
else:
prev_depreciation_amount = 0
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
depreciation_amount = get_depreciation_amount(
self,
value_after_depreciation,
finance_book,
n,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
)
if not has_pro_rata or (
n < (cint(number_of_pending_depreciations) - 1) or number_of_pending_depreciations == 2
):
schedule_date = add_months(
finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation)
)
@@ -283,7 +347,10 @@ class Asset(AccountsController):
if date_of_disposal:
from_date = self.get_from_date(finance_book.finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, date_of_disposal
finance_book,
depreciation_amount,
from_date,
date_of_disposal,
)
if depreciation_amount > 0:
@@ -301,12 +368,20 @@ class Asset(AccountsController):
break
# For first row
if has_pro_rata and not self.opening_accumulated_depreciation and n == 0:
if (
(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
) # needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, finance_book.depreciation_start_date
finance_book,
depreciation_amount,
from_date,
finance_book.depreciation_start_date,
has_wdv_or_dd_non_yearly_pro_rata,
)
# For first depr schedule date will be the start date
@@ -325,7 +400,11 @@ class Asset(AccountsController):
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, schedule_date, self.to_date
finance_book,
depreciation_amount,
schedule_date,
self.to_date,
has_wdv_or_dd_non_yearly_pro_rata,
)
depreciation_amount = self.get_adjusted_depreciation_amount(
@@ -430,28 +509,37 @@ class Asset(AccountsController):
return add_days(self.available_for_use_date, -1)
# if it returns True, depreciation_amount will not be equal for the first and last rows
def check_is_pro_rata(self, row):
def check_is_pro_rata(self, row, wdv_or_dd_non_yearly=False):
has_pro_rata = False
# if not existing asset, from_date = available_for_use_date
# otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
# from_date = 01/01/2022
from_date = self.get_modified_available_for_use_date(row)
from_date = self.get_modified_available_for_use_date(row, wdv_or_dd_non_yearly)
days = date_diff(row.depreciation_start_date, from_date) + 1
# if frequency_of_depreciation is 12 months, total_days = 365
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if wdv_or_dd_non_yearly:
total_days = get_total_days(row.depreciation_start_date, 12)
else:
# if frequency_of_depreciation is 12 months, total_days = 365
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if days < total_days:
has_pro_rata = True
return has_pro_rata
def get_modified_available_for_use_date(self, row):
return add_months(
self.available_for_use_date,
(self.number_of_depreciations_booked * row.frequency_of_depreciation),
)
def get_modified_available_for_use_date(self, row, wdv_or_dd_non_yearly=False):
if wdv_or_dd_non_yearly:
return add_months(
self.available_for_use_date,
(self.number_of_depreciations_booked * 12),
)
else:
return add_months(
self.available_for_use_date,
(self.number_of_depreciations_booked * row.frequency_of_depreciation),
)
def validate_asset_finance_books(self, row):
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
@@ -545,9 +633,7 @@ class Asset(AccountsController):
def set_accumulated_depreciation(
self, date_of_disposal=None, date_of_return=None, ignore_booked_entry=False
):
straight_line_idx = [
d.idx for d in self.get("schedules") if d.depreciation_method == "Straight Line"
]
straight_line_idx = []
finance_books = []
for i, d in enumerate(self.get("schedules")):
@@ -555,6 +641,12 @@ class Asset(AccountsController):
continue
if int(d.finance_book_id) not in finance_books:
straight_line_idx = [
s.idx
for s in self.get("schedules")
if s.finance_book_id == d.finance_book_id
and (s.depreciation_method == "Straight Line" or s.depreciation_method == "Manual")
]
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
value_after_depreciation = flt(
self.get("finance_books")[cint(d.finance_book_id) - 1].value_after_depreciation
@@ -850,22 +942,51 @@ class Asset(AccountsController):
float_precision = cint(frappe.db.get_default("float_precision")) or 2
if args.get("depreciation_method") == "Double Declining Balance":
return 200.0 / args.get("total_number_of_depreciations")
return 200.0 / (
(
flt(args.get("total_number_of_depreciations"), 2) * flt(args.get("frequency_of_depreciation"))
)
/ 12
)
if args.get("depreciation_method") == "Written Down Value":
if args.get("rate_of_depreciation") and on_validate:
if (
args.get("rate_of_depreciation")
and on_validate
and not self.flags.increase_in_asset_value_due_to_repair
):
return args.get("rate_of_depreciation")
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
if self.flags.increase_in_asset_value_due_to_repair:
value = flt(args.get("expected_value_after_useful_life")) / flt(
args.get("value_after_depreciation")
)
else:
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
depreciation_rate = math.pow(
value,
1.0
/ (
(
flt(args.get("total_number_of_depreciations"), 2)
* flt(args.get("frequency_of_depreciation"))
)
/ 12
),
)
return flt((100 * (1 - depreciation_rate)), float_precision)
def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
def get_pro_rata_amt(
self, row, depreciation_amount, from_date, to_date, has_wdv_or_dd_non_yearly_pro_rata=False
):
days = date_diff(to_date, from_date)
months = month_diff(to_date, from_date)
total_days = get_total_days(to_date, row.frequency_of_depreciation)
if has_wdv_or_dd_non_yearly_pro_rata:
total_days = get_total_days(to_date, 12)
else:
total_days = get_total_days(to_date, row.frequency_of_depreciation)
return (depreciation_amount * flt(days)) / flt(total_days), days, months
@@ -1126,20 +1247,69 @@ def get_total_days(date, frequency):
@erpnext.allow_regional
def get_depreciation_amount(asset, depreciable_value, row):
def get_depreciation_amount(
asset,
depreciable_value,
row,
schedule_idx=0,
prev_depreciation_amount=0,
has_wdv_or_dd_non_yearly_pro_rata=False,
):
if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life:
depreciation_amount = (
flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations)
# if the Depreciation Schedule is being modified after Asset Repair
else:
depreciation_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
return get_straight_line_or_manual_depr_amount(asset, row)
else:
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
return get_wdv_or_dd_depr_amount(
depreciable_value,
row.rate_of_depreciation,
row.frequency_of_depreciation,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
)
return depreciation_amount
def get_straight_line_or_manual_depr_amount(asset, row):
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
if asset.flags.increase_in_asset_life:
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
date_diff(asset.to_date, asset.available_for_use_date) / 365
)
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
elif asset.flags.increase_in_asset_value_due_to_repair:
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
)
# if the Depreciation Schedule is being prepared for the first time
else:
return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
)
def get_wdv_or_dd_depr_amount(
depreciable_value,
rate_of_depreciation,
frequency_of_depreciation,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
):
if cint(frequency_of_depreciation) == 12:
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
else:
if has_wdv_or_dd_non_yearly_pro_rata:
if schedule_idx == 0:
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1:
return (
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
)
else:
return prev_depreciation_amount
else:
if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0:
return (
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
)
else:
return prev_depreciation_amount

View File

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

View File

@@ -133,6 +133,7 @@ def make_depreciation_entry(asset_name, date=None):
je.append("accounts", debit_entry)
je.flags.ignore_permissions = True
je.flags.planned_depr_entry = True
je.save()
if not je.meta.get_workflow():
je.submit()
@@ -225,10 +226,16 @@ def notify_depr_entry_posting_error(failed_asset_names):
asset_links = get_comma_separated_asset_links(failed_asset_names)
message = (
_("Hi,")
+ "<br>"
+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
_("Hello,")
+ "<br><br>"
+ _("The following assets have failed to automatically post depreciation entries: {0}").format(
asset_links
)
+ "."
+ "<br><br>"
+ _(
"Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table."
)
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)

View File

@@ -806,12 +806,12 @@ class TestDepreciationMethods(AssetSetup):
)
expected_schedules = [
["2022-02-28", 647.25, 647.25],
["2022-03-31", 1210.71, 1857.96],
["2022-04-30", 1053.99, 2911.95],
["2022-05-31", 917.55, 3829.5],
["2022-06-30", 798.77, 4628.27],
["2022-07-15", 371.73, 5000.0],
["2022-02-28", 310.89, 310.89],
["2022-03-31", 654.45, 965.34],
["2022-04-30", 654.45, 1619.79],
["2022-05-31", 654.45, 2274.24],
["2022-06-30", 654.45, 2928.69],
["2022-07-15", 2071.31, 5000.0],
]
schedules = [
@@ -1421,7 +1421,7 @@ class TestDepreciationBasics(AssetSetup):
)
self.assertEqual(asset.status, "Submitted")
self.assertEqual(asset.get("value_after_depreciation"), 100000)
self.assertEqual(asset.get_value_after_depreciation(), 100000)
jv = make_journal_entry(
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
@@ -1434,12 +1434,68 @@ class TestDepreciationBasics(AssetSetup):
jv.submit()
asset.reload()
self.assertEqual(asset.get("value_after_depreciation"), 99900)
self.assertEqual(asset.get_value_after_depreciation(), 99900)
jv.cancel()
asset.reload()
self.assertEqual(asset.get("value_after_depreciation"), 100000)
self.assertEqual(asset.get_value_after_depreciation(), 100000)
def test_manual_depreciation_for_depreciable_asset(self):
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
purchase_date="2020-01-30",
available_for_use_date="2020-01-30",
expected_value_after_useful_life=10000,
total_number_of_depreciations=10,
frequency_of_depreciation=1,
submit=1,
)
self.assertEqual(asset.status, "Submitted")
self.assertEqual(asset.get_value_after_depreciation(), 100000)
jv = make_journal_entry(
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
)
for d in jv.accounts:
d.reference_type = "Asset"
d.reference_name = asset.name
jv.voucher_type = "Depreciation Entry"
jv.insert()
jv.submit()
asset.reload()
self.assertEqual(asset.get_value_after_depreciation(), 99900)
jv.cancel()
asset.reload()
self.assertEqual(asset.get_value_after_depreciation(), 100000)
def test_manual_depreciation_with_incorrect_jv_voucher_type(self):
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
purchase_date="2020-01-30",
available_for_use_date="2020-01-30",
expected_value_after_useful_life=10000,
total_number_of_depreciations=10,
frequency_of_depreciation=1,
submit=1,
)
jv = make_journal_entry(
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
)
for d in jv.accounts:
d.reference_type = "Asset"
d.reference_name = asset.name
d.account_type = "Depreciation"
jv.voucher_type = "Journal Entry"
self.assertRaises(frappe.ValidationError, jv.insert)
def create_asset_data():

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,7 @@ frappe.query_reports["Fixed Asset Register"] = {
"label": __("Period Based On"),
"fieldtype": "Select",
"options": ["Fiscal Year", "Date Range"],
"default": ["Fiscal Year"],
"default": "Fiscal Year",
"reqd": 1
},
{
@@ -75,12 +75,6 @@ frappe.query_reports["Fixed Asset Register"] = {
fieldtype: "Link",
options: "Asset Category"
},
{
fieldname:"finance_book",
label: __("Finance Book"),
fieldtype: "Link",
options: "Finance Book"
},
{
fieldname:"cost_center",
label: __("Cost Center"),
@@ -96,8 +90,20 @@ frappe.query_reports["Fixed Asset Register"] = {
reqd: 1
},
{
fieldname:"is_existing_asset",
label: __("Is Existing Asset"),
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"
},
]

View File

@@ -45,8 +45,8 @@ def get_conditions(filters):
filters.year_end_date = getdate(fiscal_year.year_end_date)
conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
if filters.get("is_existing_asset"):
conditions["is_existing_asset"] = filters.get("is_existing_asset")
if filters.get("only_existing_assets"):
conditions["is_existing_asset"] = filters.get("only_existing_assets")
if filters.get("asset_category"):
conditions["asset_category"] = filters.get("asset_category")
if filters.get("cost_center"):
@@ -102,19 +102,18 @@ def get_data(filters):
]
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
assets_linked_to_fb = frappe.db.get_all(
doctype="Asset Finance Book",
filters={"finance_book": filters.finance_book or ("is", "not set")},
pluck="parent",
)
assets_linked_to_fb = None
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",
)
for asset in assets_record:
if filters.finance_book:
if asset.asset_id not in assets_linked_to_fb:
continue
else:
if asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb:
continue
if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb:
continue
asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book)
row = {
@@ -172,11 +171,11 @@ def prepare_chart_data(data, filters):
"datasets": [
{
"name": _("Asset Value"),
"values": [d.get("asset_value") for d in labels_values_map.values()],
"values": [flt(d.get("asset_value"), 2) for d in labels_values_map.values()],
},
{
"name": _("Depreciatied Amount"),
"values": [d.get("depreciated_amount") for d in labels_values_map.values()],
"values": [flt(d.get("depreciated_amount"), 2) for d in labels_values_map.values()],
},
],
},
@@ -310,7 +309,7 @@ def get_columns(filters):
return [
{
"label": _("Asset Id"),
"label": _("Asset ID"),
"fieldtype": "Link",
"fieldname": "asset_id",
"options": "Asset",

View File

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

View File

@@ -191,11 +191,15 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
cur_frm.add_custom_button(__('Purchase Invoice'),
this.make_purchase_invoice, __('Create'));
if(flt(doc.per_billed)==0 && doc.status != "Delivered") {
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
if(flt(doc.per_billed) < 100 && doc.status != "Delivered") {
this.frm.add_custom_button(
__('Payment'),
() => this.make_payment_entry(),
__('Create')
);
}
if(flt(doc.per_billed)==0) {
if(flt(doc.per_billed) < 100) {
this.frm.add_custom_button(__('Payment Request'),
function() { me.make_payment_request() }, __('Create'));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -131,7 +131,7 @@ def validate_returned_items(doc):
)
elif ref.serial_no:
if not d.serial_no:
if d.qty and not d.serial_no:
frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
else:
serial_nos = get_serial_nos(d.serial_no)
@@ -301,7 +301,7 @@ def get_returned_qty_map_for_row(return_against, party, row_name, doctype):
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
# Used retrun against and supplier and is_retrun because there is an index added for it
data = frappe.db.get_list(
data = frappe.get_all(
doctype,
fields=fields,
filters=[
@@ -393,6 +393,16 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
if serial_nos:
target_doc.serial_no = "\n".join(serial_nos)
if source_doc.get("rejected_serial_no"):
returned_serial_nos = get_returned_serial_nos(
source_doc, source_parent, serial_no_field="rejected_serial_no"
)
rejected_serial_nos = list(
set(get_serial_nos(source_doc.rejected_serial_no)) - set(returned_serial_nos)
)
if rejected_serial_nos:
target_doc.rejected_serial_no = "\n".join(rejected_serial_nos)
if doctype == "Purchase Receipt":
returned_qty_map = get_returned_qty_map_for_row(
source_parent.name, source_parent.supplier, source_doc.name, doctype
@@ -587,7 +597,7 @@ def get_filters(
return filters
def get_returned_serial_nos(child_doc, parent_doc):
def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
return_ref_field = frappe.scrub(child_doc.doctype)
@@ -596,7 +606,7 @@ def get_returned_serial_nos(child_doc, parent_doc):
serial_nos = []
fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
fields = [f"`{'tab' + child_doc.doctype}`.`{serial_no_field}`"]
filters = [
[parent_doc.doctype, "return_against", "=", parent_doc.name],
@@ -606,6 +616,6 @@ def get_returned_serial_nos(child_doc, parent_doc):
]
for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
serial_nos.extend(get_serial_nos(row.serial_no))
serial_nos.extend(get_serial_nos(row.get(serial_no_field)))
return serial_nos

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -90,6 +90,7 @@ class LeaveAllocation(Document):
if self.carry_forward:
self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True)
# nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
def on_update_after_submit(self):
if self.has_value_changed("new_leaves_allocated"):
self.validate_against_leave_applications()
@@ -99,7 +100,11 @@ class LeaveAllocation(Document):
# run required validations again since total leaves are being updated
self.validate_leave_days_and_dates()
leaves_to_be_added = self.new_leaves_allocated - self.get_existing_leave_count()
leaves_to_be_added = flt(
(self.new_leaves_allocated - self.get_existing_leave_count()),
self.precision("new_leaves_allocated"),
)
args = {
"leaves": leaves_to_be_added,
"from_date": self.from_date,
@@ -118,14 +123,13 @@ class LeaveAllocation(Document):
"employee": self.employee,
"company": self.company,
"leave_type": self.leave_type,
"is_carry_forward": 0,
"docstatus": 1,
},
pluck="leaves",
fields=["SUM(leaves) as total_leaves"],
)
total_existing_leaves = 0
for entry in ledger_entries:
total_existing_leaves += entry
return total_existing_leaves
return ledger_entries[0].total_leaves if ledger_entries else 0
def validate_against_leave_applications(self):
leaves_taken = get_approved_leaves_for_period(

View File

@@ -18,6 +18,7 @@ class TestLeaveAllocation(FrappeTestCase):
def setUp(self):
frappe.db.delete("Leave Period")
frappe.db.delete("Leave Allocation")
frappe.db.delete("Leave Ledger Entry")
emp_id = make_employee("test_emp_leave_allocation@salary.com", company="_Test Company")
self.employee = frappe.get_doc("Employee", emp_id)
@@ -69,7 +70,6 @@ class TestLeaveAllocation(FrappeTestCase):
def test_validation_for_over_allocation(self):
leave_type = create_leave_type(leave_type_name="Test Over Allocation", is_carry_forward=1)
leave_type.save()
doc = frappe.get_doc(
{
@@ -137,9 +137,9 @@ class TestLeaveAllocation(FrappeTestCase):
)
).insert()
leave_type = create_leave_type(leave_type_name="_Test Allocation Validation", is_carry_forward=1)
leave_type.max_leaves_allowed = 25
leave_type.save()
leave_type = create_leave_type(
leave_type_name="_Test Allocation Validation", is_carry_forward=1, max_leaves_allowed=25
)
# 15 leaves allocated in this period
allocation = create_leave_allocation(
@@ -174,9 +174,9 @@ class TestLeaveAllocation(FrappeTestCase):
)
).insert()
leave_type = create_leave_type(leave_type_name="_Test Allocation Validation", is_carry_forward=1)
leave_type.max_leaves_allowed = 30
leave_type.save()
leave_type = create_leave_type(
leave_type_name="_Test Allocation Validation", is_carry_forward=1, max_leaves_allowed=30
)
# 15 leaves allocated
allocation = create_leave_allocation(
@@ -207,7 +207,6 @@ class TestLeaveAllocation(FrappeTestCase):
def test_validate_back_dated_allocation_update(self):
leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1)
leave_type.save()
# initial leave allocation = 15
leave_allocation = create_leave_allocation(
@@ -235,10 +234,12 @@ class TestLeaveAllocation(FrappeTestCase):
self.assertRaises(BackDatedAllocationError, leave_allocation.save)
def test_carry_forward_calculation(self):
leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1)
leave_type.maximum_carry_forwarded_leaves = 10
leave_type.max_leaves_allowed = 30
leave_type.save()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave",
is_carry_forward=1,
maximum_carry_forwarded_leaves=10,
max_leaves_allowed=30,
)
# initial leave allocation = 15
leave_allocation = create_leave_allocation(
@@ -286,7 +287,6 @@ class TestLeaveAllocation(FrappeTestCase):
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90,
)
leave_type.save()
# initial leave allocation
leave_allocation = create_leave_allocation(
@@ -352,12 +352,51 @@ class TestLeaveAllocation(FrappeTestCase):
)
leave_allocation.submit()
leave_allocation.reload()
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
self.assertEqual(leave_allocation.total_leaves_allocated, 15)
leave_allocation.new_leaves_allocated = 40
leave_allocation.submit()
leave_allocation.save()
leave_allocation.reload()
self.assertTrue(leave_allocation.total_leaves_allocated, 40)
updated_entry = frappe.db.get_all(
"Leave Ledger Entry",
{"transaction_name": leave_allocation.name},
pluck="leaves",
order_by="creation desc",
limit=1,
)
self.assertEqual(updated_entry[0], 25)
self.assertEqual(leave_allocation.total_leaves_allocated, 40)
def test_leave_addition_after_submit_with_carry_forward(self):
from erpnext.hr.doctype.leave_application.test_leave_application import (
create_carry_forwarded_allocation,
)
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
include_holiday=True,
)
leave_allocation = create_carry_forwarded_allocation(self.employee, leave_type)
# 15 new leaves, 15 carry forwarded leaves
self.assertEqual(leave_allocation.total_leaves_allocated, 30)
leave_allocation.new_leaves_allocated = 32
leave_allocation.save()
leave_allocation.reload()
updated_entry = frappe.db.get_all(
"Leave Ledger Entry",
{"transaction_name": leave_allocation.name},
pluck="leaves",
order_by="creation desc",
limit=1,
)
self.assertEqual(updated_entry[0], 17)
self.assertEqual(leave_allocation.total_leaves_allocated, 47)
def test_leave_subtraction_after_submit(self):
leave_allocation = create_leave_allocation(
@@ -365,12 +404,49 @@ class TestLeaveAllocation(FrappeTestCase):
)
leave_allocation.submit()
leave_allocation.reload()
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
self.assertEqual(leave_allocation.total_leaves_allocated, 15)
leave_allocation.new_leaves_allocated = 10
leave_allocation.submit()
leave_allocation.reload()
self.assertTrue(leave_allocation.total_leaves_allocated, 10)
updated_entry = frappe.db.get_all(
"Leave Ledger Entry",
{"transaction_name": leave_allocation.name},
pluck="leaves",
order_by="creation desc",
limit=1,
)
self.assertEqual(updated_entry[0], -5)
self.assertEqual(leave_allocation.total_leaves_allocated, 10)
def test_leave_subtraction_after_submit_with_carry_forward(self):
from erpnext.hr.doctype.leave_application.test_leave_application import (
create_carry_forwarded_allocation,
)
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
include_holiday=True,
)
leave_allocation = create_carry_forwarded_allocation(self.employee, leave_type)
self.assertEqual(leave_allocation.total_leaves_allocated, 30)
leave_allocation.new_leaves_allocated = 8
leave_allocation.save()
updated_entry = frappe.db.get_all(
"Leave Ledger Entry",
{"transaction_name": leave_allocation.name},
pluck="leaves",
order_by="creation desc",
limit=1,
)
self.assertEqual(updated_entry[0], -7)
self.assertEqual(leave_allocation.total_leaves_allocated, 23)
def test_validation_against_leave_application_after_submit(self):
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list

View File

@@ -817,7 +817,9 @@ def get_leave_balance_on(
allocation = allocation_records.get(leave_type, frappe._dict())
end_date = allocation.to_date if cint(consider_all_leaves_in_the_allocation_period) else date
cf_expiry = get_allocation_expiry_for_cf_leaves(employee, leave_type, to_date, date)
cf_expiry = get_allocation_expiry_for_cf_leaves(
employee, leave_type, to_date, allocation.from_date
)
leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, end_date)
@@ -832,6 +834,7 @@ def get_leave_balance_on(
def get_leave_allocation_records(employee, date, leave_type=None):
"""Returns the total allocated leaves and carry forwarded leaves based on ledger entries"""
Ledger = frappe.qb.DocType("Leave Ledger Entry")
LeaveAllocation = frappe.qb.DocType("Leave Allocation")
cf_leave_case = (
frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
@@ -845,21 +848,36 @@ def get_leave_allocation_records(employee, date, leave_type=None):
query = (
frappe.qb.from_(Ledger)
.inner_join(LeaveAllocation)
.on(Ledger.transaction_name == LeaveAllocation.name)
.select(
sum_cf_leaves,
sum_new_leaves,
Min(Ledger.from_date).as_("from_date"),
Max(Ledger.to_date).as_("to_date"),
Ledger.leave_type,
Ledger.employee,
)
.where(
(Ledger.from_date <= date)
& (Ledger.to_date >= date)
& (Ledger.docstatus == 1)
& (Ledger.transaction_type == "Leave Allocation")
& (Ledger.employee == employee)
& (Ledger.is_expired == 0)
& (Ledger.is_lwp == 0)
& (
# newly allocated leave's end date is same as the leave allocation's to date
((Ledger.is_carry_forward == 0) & (Ledger.to_date >= date))
# carry forwarded leave's end date won't be same as the leave allocation's to date
# it's between the leave allocation's from and to date
| (
(Ledger.is_carry_forward == 1)
& (Ledger.to_date.between(LeaveAllocation.from_date, LeaveAllocation.to_date))
# only consider cf leaves from current allocation
& (LeaveAllocation.from_date <= date)
& (date <= LeaveAllocation.to_date)
)
)
)
)
@@ -881,6 +899,7 @@ def get_leave_allocation_records(employee, date, leave_type=None):
"unused_leaves": d.cf_leaves,
"new_leaves_allocated": d.new_leaves,
"leave_type": d.leave_type,
"employee": d.employee,
}
),
)
@@ -919,22 +938,51 @@ def get_remaining_leaves(
return remaining_leaves
leave_balance = leave_balance_for_consumption = flt(allocation.total_leaves_allocated) + flt(
leaves_taken
)
# balance for carry forwarded leaves
if cf_expiry and allocation.unused_leaves:
cf_leaves = flt(allocation.unused_leaves) + flt(leaves_taken)
remaining_cf_leaves = _get_remaining_leaves(cf_leaves, cf_expiry)
# allocation contains both carry forwarded and new leaves
new_leaves_taken, cf_leaves_taken = get_new_and_cf_leaves_taken(allocation, cf_expiry)
leave_balance = flt(allocation.new_leaves_allocated) + flt(cf_leaves)
leave_balance_for_consumption = flt(allocation.new_leaves_allocated) + flt(remaining_cf_leaves)
if getdate(date) > getdate(cf_expiry):
# carry forwarded leaves have expired
cf_leaves = remaining_cf_leaves = 0
else:
cf_leaves = flt(allocation.unused_leaves) + flt(cf_leaves_taken)
remaining_cf_leaves = _get_remaining_leaves(cf_leaves, cf_expiry)
# new leaves allocated - new leaves taken + cf leave balance
# Note: `new_leaves_taken` is added here because its already a -ve number in the ledger
leave_balance = (flt(allocation.new_leaves_allocated) + flt(new_leaves_taken)) + flt(cf_leaves)
leave_balance_for_consumption = (
flt(allocation.new_leaves_allocated) + flt(new_leaves_taken)
) + flt(remaining_cf_leaves)
else:
# allocation only contains newly allocated leaves
leave_balance = leave_balance_for_consumption = flt(allocation.total_leaves_allocated) + flt(
leaves_taken
)
remaining_leaves = _get_remaining_leaves(leave_balance_for_consumption, allocation.to_date)
return frappe._dict(leave_balance=leave_balance, leave_balance_for_consumption=remaining_leaves)
def get_new_and_cf_leaves_taken(allocation: Dict, cf_expiry: str) -> Tuple[float, float]:
"""returns new leaves taken and carry forwarded leaves taken within an allocation period based on cf leave expiry"""
cf_leaves_taken = get_leaves_for_period(
allocation.employee, allocation.leave_type, allocation.from_date, cf_expiry
)
new_leaves_taken = get_leaves_for_period(
allocation.employee, allocation.leave_type, add_days(cf_expiry, 1), allocation.to_date
)
# using abs because leaves taken is a -ve number in the ledger
if abs(cf_leaves_taken) > allocation.unused_leaves:
# adjust the excess leaves in new_leaves_taken
new_leaves_taken += -(abs(cf_leaves_taken) - allocation.unused_leaves)
cf_leaves_taken = -allocation.unused_leaves
return new_leaves_taken, cf_leaves_taken
def get_leaves_for_period(
employee: str, leave_type: str, from_date: str, to_date: str, skip_expired_leaves: bool = True
) -> float:

View File

@@ -28,6 +28,7 @@ from erpnext.hr.doctype.leave_application.leave_application import (
get_leave_allocation_records,
get_leave_balance_on,
get_leave_details,
get_new_and_cf_leaves_taken,
)
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
create_assignment_for_multiple_employees,
@@ -96,6 +97,9 @@ class TestLeaveApplication(unittest.TestCase):
from_date = get_year_start(getdate())
to_date = get_year_ending(getdate())
self.holiday_list = make_holiday_list(from_date=from_date, to_date=to_date)
list_without_weekly_offs = make_holiday_list(
"Holiday List w/o Weekly Offs", from_date=from_date, to_date=to_date, add_weekly_offs=False
)
if not frappe.db.exists("Leave Type", "_Test Leave Type"):
frappe.get_doc(
@@ -699,7 +703,6 @@ class TestLeaveApplication(unittest.TestCase):
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90,
)
leave_type.insert()
create_carry_forwarded_allocation(employee, leave_type)
details = get_leave_balance_on(
@@ -771,7 +774,6 @@ class TestLeaveApplication(unittest.TestCase):
employee = get_employee()
leave_type = create_leave_type(leave_type_name="Test Leave Type 1")
leave_type.save()
leave_allocation = create_leave_allocation(
employee=employee.name, employee_name=employee.employee_name, leave_type=leave_type.name
@@ -814,7 +816,6 @@ class TestLeaveApplication(unittest.TestCase):
expire_carry_forwarded_leaves_after_days=90,
include_holiday=True,
)
leave_type.submit()
create_carry_forwarded_allocation(employee, leave_type)
@@ -853,7 +854,6 @@ class TestLeaveApplication(unittest.TestCase):
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90,
)
leave_type.submit()
create_carry_forwarded_allocation(employee, leave_type)
@@ -991,18 +991,169 @@ class TestLeaveApplication(unittest.TestCase):
}
self.assertEqual(leave_allocation, expected)
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_get_leave_allocation_records(self):
@set_holiday_list("Holiday List w/o Weekly Offs", "_Test Company")
def test_leave_details_with_expired_cf_leaves(self):
"""Tests leave details:
Case 1: All leaves available before cf leave expiry
Case 2: Remaining Leaves after cf leave expiry
"""
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90,
)
leave_type.insert()
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
details = get_leave_allocation_records(employee.name, getdate(), leave_type.name)
cf_expiry = frappe.db.get_value(
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
)
# case 1: all leaves available before cf leave expiry
leave_details = get_leave_details(employee.name, add_days(cf_expiry, -1))
self.assertEqual(leave_details["leave_allocation"][leave_type.name]["remaining_leaves"], 30.0)
# case 2: cf leaves expired
leave_details = get_leave_details(employee.name, add_days(cf_expiry, 1))
expected_data = {
"total_leaves": 30.0,
"expired_leaves": 15.0,
"leaves_taken": 0.0,
"leaves_pending_approval": 0.0,
"remaining_leaves": 15.0,
}
self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data)
@set_holiday_list("Holiday List w/o Weekly Offs", "_Test Company")
def test_leave_details_with_application_across_cf_expiry(self):
"""Tests leave details with leave application across cf expiry, such that:
cf leaves are partially expired and partially consumed
"""
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90,
)
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
cf_expiry = frappe.db.get_value(
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
)
# leave application across cf expiry
application = make_leave_application(
employee.name,
cf_expiry,
add_days(cf_expiry, 3),
leave_type.name,
)
leave_details = get_leave_details(employee.name, add_days(cf_expiry, 4))
expected_data = {
"total_leaves": 30.0,
"expired_leaves": 14.0,
"leaves_taken": 4.0,
"leaves_pending_approval": 0.0,
"remaining_leaves": 12.0,
}
self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data)
@set_holiday_list("Holiday List w/o Weekly Offs", "_Test Company")
def test_leave_details_with_application_across_cf_expiry_2(self):
"""Tests the same case as above but with leave days greater than cf leaves allocated"""
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90,
)
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
cf_expiry = frappe.db.get_value(
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
)
# leave application across cf expiry, 20 days leave
application = make_leave_application(
employee.name,
add_days(cf_expiry, -16),
add_days(cf_expiry, 3),
leave_type.name,
)
# 15 cf leaves and 5 new leaves should be consumed
# after adjustment of the actual days breakup (17 and 3) because only 15 cf leaves have been allocated
new_leaves_taken, cf_leaves_taken = get_new_and_cf_leaves_taken(leave_alloc, cf_expiry)
self.assertEqual(new_leaves_taken, -5.0)
self.assertEqual(cf_leaves_taken, -15.0)
leave_details = get_leave_details(employee.name, add_days(cf_expiry, 4))
expected_data = {
"total_leaves": 30.0,
"expired_leaves": 0,
"leaves_taken": 20.0,
"leaves_pending_approval": 0.0,
"remaining_leaves": 10.0,
}
self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data)
@set_holiday_list("Holiday List w/o Weekly Offs", "_Test Company")
def test_leave_details_with_application_after_cf_expiry(self):
"""Tests leave details with leave application after cf expiry, such that:
cf leaves are completely expired and only newly allocated leaves are consumed
"""
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90,
)
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
cf_expiry = frappe.db.get_value(
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
)
# leave application after cf expiry
application = make_leave_application(
employee.name,
add_days(cf_expiry, 1),
add_days(cf_expiry, 4),
leave_type.name,
)
leave_details = get_leave_details(employee.name, add_days(cf_expiry, 4))
expected_data = {
"total_leaves": 30.0,
"expired_leaves": 15.0,
"leaves_taken": 4.0,
"leaves_pending_approval": 0.0,
"remaining_leaves": 11.0,
}
self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data)
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_get_leave_allocation_records(self):
"""Tests if total leaves allocated before and after carry forwarded leave expiry is same"""
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90,
)
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
cf_expiry = frappe.db.get_value(
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
)
# test total leaves allocated before cf leave expiry
details = get_leave_allocation_records(employee.name, add_days(cf_expiry, -1), leave_type.name)
expected_data = {
"from_date": getdate(leave_alloc.from_date),
"to_date": getdate(leave_alloc.to_date),
@@ -1010,28 +1161,60 @@ class TestLeaveApplication(unittest.TestCase):
"unused_leaves": 15.0,
"new_leaves_allocated": 15.0,
"leave_type": leave_type.name,
"employee": employee.name,
}
self.assertEqual(details.get(leave_type.name), expected_data)
# test leaves allocated after carry forwarded leaves expiry, should be same thoroughout allocation period
# cf leaves should show up under expired or taken leaves later
details = get_leave_allocation_records(employee.name, add_days(cf_expiry, 1), leave_type.name)
self.assertEqual(details.get(leave_type.name), expected_data)
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_filtered_old_cf_entries_in_get_leave_allocation_records(self):
"""Tests whether old cf entries are ignored while fetching current allocation records"""
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90,
)
# old allocation with cf leaves
create_carry_forwarded_allocation(employee, leave_type, date="2019-01-01")
# new allocation with cf leaves
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
cf_expiry = frappe.db.get_value(
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
)
# test total leaves allocated before cf leave expiry
details = get_leave_allocation_records(employee.name, add_days(cf_expiry, -1), leave_type.name)
# filters out old CF leaves (15 i.e total 45)
self.assertEqual(details[leave_type.name]["total_leaves_allocated"], 30.0)
def create_carry_forwarded_allocation(employee, leave_type, date=None):
date = date or nowdate()
def create_carry_forwarded_allocation(employee, leave_type):
# initial leave allocation
leave_allocation = create_leave_allocation(
leave_type="_Test_CF_leave_expiry",
employee=employee.name,
employee_name=employee.employee_name,
from_date=add_months(nowdate(), -24),
to_date=add_months(nowdate(), -12),
from_date=add_months(date, -24),
to_date=add_months(date, -12),
carry_forward=0,
)
leave_allocation.submit()
# carry forward leave allocation
leave_allocation = create_leave_allocation(
leave_type="_Test_CF_leave_expiry",
employee=employee.name,
employee_name=employee.employee_name,
from_date=add_days(nowdate(), -84),
to_date=add_days(nowdate(), 100),
from_date=add_days(date, -84),
to_date=add_days(date, 100),
carry_forward=1,
)
leave_allocation.submit()

View File

@@ -9,7 +9,8 @@ test_records = frappe.get_test_records("Leave Type")
def create_leave_type(**args):
args = frappe._dict(args)
if frappe.db.exists("Leave Type", args.leave_type_name):
return frappe.get_doc("Leave Type", args.leave_type_name)
frappe.delete_doc("Leave Type", args.leave_type_name, force=True)
leave_type = frappe.get_doc(
{
"doctype": "Leave Type",
@@ -23,10 +24,14 @@ def create_leave_type(**args):
"expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
"encashment_threshold_days": args.encashment_threshold_days or 5,
"earning_component": "Leave Encashment",
"max_leaves_allowed": args.max_leaves_allowed,
"maximum_carry_forwarded_leaves": args.maximum_carry_forwarded_leaves,
}
)
if leave_type.is_ppl:
leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5
leave_type.insert()
return leave_type

View File

@@ -154,7 +154,6 @@ class TestEmployeeLeaveBalance(unittest.TestCase):
@set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_opening_balance_considers_carry_forwarded_leaves(self):
leave_type = create_leave_type(leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1)
leave_type.insert()
# 30 leaves allocated for first half of the year
allocation1 = make_allocation_record(

View File

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

View File

@@ -64,8 +64,6 @@
"fieldtype": "Section Break"
},
{
"fetch_from": "prevdoc_detail_docname.sales_person",
"fetch_if_empty": 1,
"fieldname": "service_person",
"fieldtype": "Link",
"in_list_view": 1,
@@ -110,13 +108,15 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-05-27 17:47:21.474282",
"modified": "2023-02-27 11:09:33.114458",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit Purpose",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ class BOMTree:
# specifying the attributes to save resources
# ref: https://docs.python.org/3/reference/datamodel.html#slots
__slots__ = ["name", "child_items", "is_bom", "item_code", "exploded_qty", "qty"]
__slots__ = ["name", "child_items", "is_bom", "item_code", "qty", "exploded_qty", "bom_qty"]
def __init__(
self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1
@@ -50,9 +50,10 @@ class BOMTree:
def __create_tree(self):
bom = frappe.get_cached_doc("BOM", self.name)
self.item_code = bom.item
self.bom_qty = bom.quantity
for item in bom.get("items", []):
qty = item.qty / bom.quantity # quantity per unit
qty = item.stock_qty / bom.quantity # quantity per unit
exploded_qty = self.exploded_qty * qty
if item.bom_no:
child = BOMTree(item.bom_no, exploded_qty=exploded_qty, qty=qty)
@@ -948,7 +949,8 @@ def get_valuation_rate(data):
2) If no value, get last valuation rate from SLE
3) If no value, get valuation rate from Item
"""
from frappe.query_builder.functions import Sum
from frappe.query_builder.functions import Count, IfNull, Sum
from pypika import Case
item_code, company = data.get("item_code"), data.get("company")
valuation_rate = 0.0
@@ -959,7 +961,14 @@ def get_valuation_rate(data):
frappe.qb.from_(bin_table)
.join(wh_table)
.on(bin_table.warehouse == wh_table.name)
.select((Sum(bin_table.stock_value) / Sum(bin_table.actual_qty)).as_("valuation_rate"))
.select(
Case()
.when(
Count(bin_table.name) > 0, IfNull(Sum(bin_table.stock_value) / Sum(bin_table.actual_qty), 0.0)
)
.else_(None)
.as_("valuation_rate")
)
.where((bin_table.item_code == item_code) & (wh_table.company == company))
).run(as_dict=True)[0]

View File

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

View File

@@ -536,7 +536,34 @@ class JobCard(Document):
)
def set_transferred_qty_in_job_card_item(self, ste_doc):
from frappe.query_builder.functions import Sum
def _get_job_card_items_transferred_qty(ste_doc):
from frappe.query_builder.functions import Sum
job_card_items_transferred_qty = {}
job_card_items = [
x.get("job_card_item") for x in ste_doc.get("items") if x.get("job_card_item")
]
if job_card_items:
se = frappe.qb.DocType("Stock Entry")
sed = frappe.qb.DocType("Stock Entry Detail")
query = (
frappe.qb.from_(sed)
.join(se)
.on(sed.parent == se.name)
.select(sed.job_card_item, Sum(sed.qty))
.where(
(sed.job_card_item.isin(job_card_items))
& (se.docstatus == 1)
& (se.purpose == "Material Transfer for Manufacture")
)
.groupby(sed.job_card_item)
)
job_card_items_transferred_qty = frappe._dict(query.run(as_list=True))
return job_card_items_transferred_qty
def _validate_over_transfer(row, transferred_qty):
"Block over transfer of items if not allowed in settings."
@@ -553,29 +580,23 @@ class JobCard(Document):
exc=JobCardOverTransferError,
)
for row in ste_doc.items:
if not row.job_card_item:
continue
sed = frappe.qb.DocType("Stock Entry Detail")
se = frappe.qb.DocType("Stock Entry")
transferred_qty = (
frappe.qb.from_(sed)
.join(se)
.on(sed.parent == se.name)
.select(Sum(sed.qty))
.where(
(sed.job_card_item == row.job_card_item)
& (se.docstatus == 1)
& (se.purpose == "Material Transfer for Manufacture")
)
).run()[0][0]
job_card_items_transferred_qty = _get_job_card_items_transferred_qty(ste_doc)
if job_card_items_transferred_qty:
allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
if not allow_excess:
_validate_over_transfer(row, transferred_qty)
frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty))
for row in ste_doc.items:
if not row.job_card_item:
continue
transferred_qty = flt(job_card_items_transferred_qty.get(row.job_card_item))
if not allow_excess:
_validate_over_transfer(row, transferred_qty)
frappe.db.set_value(
"Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty)
)
def set_transferred_qty(self, update_status=False):
"Set total FG Qty in Job Card for which RM was transferred."

View File

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

View File

@@ -476,7 +476,7 @@ frappe.ui.form.on("Work Order Item", {
callback: function(r) {
if (r.message) {
frappe.model.set_value(cdt, cdn, {
"required_qty": 1,
"required_qty": row.required_qty || 1,
"item_name": r.message.item_name,
"description": r.message.description,
"source_warehouse": r.message.default_warehouse,
@@ -592,20 +592,18 @@ erpnext.work_order = {
// all materials transferred for manufacturing, make this primary
finish_btn.addClass('btn-primary');
}
} else {
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
let allowance_percentage = doc.overproduction_percentage_for_work_order;
} else if (frm.doc.__onload && frm.doc.__onload.overproduction_percentage) {
let allowance_percentage = frm.doc.__onload.overproduction_percentage;
if (allowance_percentage > 0) {
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
if (allowance_percentage > 0) {
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
if ((flt(doc.produced_qty) < allowed_qty)) {
frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
}
if ((flt(doc.produced_qty) < allowed_qty)) {
frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
}
});
}
}
}
} else {

View File

@@ -690,7 +690,7 @@ class WorkOrder(Document):
for node in bom_traversal:
if node.is_bom:
operations.extend(_get_operations(node.name, qty=node.exploded_qty))
operations.extend(_get_operations(node.name, qty=node.exploded_qty / node.bom_qty))
bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity")
operations.extend(_get_operations(self.bom_no, qty=1.0 / bom_qty))

View File

@@ -25,8 +25,9 @@ frappe.query_reports["BOM Stock Report"] = {
],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.id == "item") {
if (data["enough_parts_to_build"] > 0) {
if (data["in_stock_qty"] >= data["required_qty"]) {
value = `<a style='color:green' href="/app/item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
} else {
value = `<a style='color:red' href="/app/item/${data['item']}" data-doctype="Item">${data['item']}</a>`;

View File

@@ -4,7 +4,8 @@
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
from frappe.query_builder.functions import Floor, Sum
from frappe.utils import cint
from pypika.terms import ExistsCriterion
@@ -34,57 +35,55 @@ def get_columns():
def get_bom_stock(filters):
qty_to_produce = filters.get("qty_to_produce") or 1
if int(qty_to_produce) < 0:
frappe.throw(_("Quantity to Produce can not be less than Zero"))
qty_to_produce = filters.get("qty_to_produce")
if cint(qty_to_produce) <= 0:
frappe.throw(_("Quantity to Produce should be greater than zero."))
if filters.get("show_exploded_view"):
bom_item_table = "BOM Explosion Item"
else:
bom_item_table = "BOM Item"
bin = frappe.qb.DocType("Bin")
bom = frappe.qb.DocType("BOM")
bom_item = frappe.qb.DocType(bom_item_table)
query = (
frappe.qb.from_(bom)
.inner_join(bom_item)
.on(bom.name == bom_item.parent)
.left_join(bin)
.on(bom_item.item_code == bin.item_code)
.select(
bom_item.item_code,
bom_item.description,
bom_item.stock_qty,
bom_item.stock_uom,
(bom_item.stock_qty / bom.quantity) * qty_to_produce,
Sum(bin.actual_qty),
Sum(bin.actual_qty) / (bom_item.stock_qty / bom.quantity),
)
.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
.groupby(bom_item.item_code)
warehouse_details = frappe.db.get_value(
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
)
if filters.get("warehouse"):
warehouse_details = frappe.db.get_value(
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
)
BOM = frappe.qb.DocType("BOM")
BOM_ITEM = frappe.qb.DocType(bom_item_table)
BIN = frappe.qb.DocType("Bin")
WH = frappe.qb.DocType("Warehouse")
CONDITIONS = ()
if warehouse_details:
wh = frappe.qb.DocType("Warehouse")
query = query.where(
ExistsCriterion(
frappe.qb.from_(wh)
.select(wh.name)
.where(
(wh.lft >= warehouse_details.lft)
& (wh.rgt <= warehouse_details.rgt)
& (bin.warehouse == wh.name)
)
)
if warehouse_details:
CONDITIONS = ExistsCriterion(
frappe.qb.from_(WH)
.select(WH.name)
.where(
(WH.lft >= warehouse_details.lft)
& (WH.rgt <= warehouse_details.rgt)
& (BIN.warehouse == WH.name)
)
else:
query = query.where(bin.warehouse == filters.get("warehouse"))
)
else:
CONDITIONS = BIN.warehouse == filters.get("warehouse")
return query.run()
QUERY = (
frappe.qb.from_(BOM)
.inner_join(BOM_ITEM)
.on(BOM.name == BOM_ITEM.parent)
.left_join(BIN)
.on((BOM_ITEM.item_code == BIN.item_code) & (CONDITIONS))
.select(
BOM_ITEM.item_code,
BOM_ITEM.description,
BOM_ITEM.stock_qty,
BOM_ITEM.stock_uom,
BOM_ITEM.stock_qty * qty_to_produce / BOM.quantity,
Sum(BIN.actual_qty).as_("actual_qty"),
Sum(Floor(BIN.actual_qty / (BOM_ITEM.stock_qty * qty_to_produce / BOM.quantity))),
)
.where((BOM_ITEM.parent == filters.get("bom")) & (BOM_ITEM.parenttype == "BOM"))
.groupby(BOM_ITEM.item_code)
)
return QUERY.run()

View File

@@ -0,0 +1,110 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe.exceptions import ValidationError
from frappe.tests.utils import FrappeTestCase
from frappe.utils import floor
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.report.bom_stock_report.bom_stock_report import (
get_bom_stock as bom_stock_report,
)
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestBomStockReport(FrappeTestCase):
def setUp(self):
self.warehouse = "_Test Warehouse - _TC"
self.fg_item, self.rm_items = create_items()
make_stock_entry(target=self.warehouse, item_code=self.rm_items[0], qty=20, basic_rate=100)
make_stock_entry(target=self.warehouse, item_code=self.rm_items[1], qty=40, basic_rate=200)
self.bom = make_bom(item=self.fg_item, quantity=1, raw_materials=self.rm_items, rm_qty=10)
def test_bom_stock_report(self):
# Test 1: When `qty_to_produce` is 0.
filters = frappe._dict(
{
"bom": self.bom.name,
"warehouse": "Stores - _TC",
"qty_to_produce": 0,
}
)
self.assertRaises(ValidationError, bom_stock_report, filters)
# Test 2: When stock is not available.
data = bom_stock_report(
frappe._dict(
{
"bom": self.bom.name,
"warehouse": "Stores - _TC",
"qty_to_produce": 1,
}
)
)
expected_data = get_expected_data(self.bom, "Stores - _TC", 1)
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
# Test 3: When stock is available.
data = bom_stock_report(
frappe._dict(
{
"bom": self.bom.name,
"warehouse": self.warehouse,
"qty_to_produce": 1,
}
)
)
expected_data = get_expected_data(self.bom, self.warehouse, 1)
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
def create_items():
fg_item = make_item(properties={"is_stock_item": 1}).name
rm_item1 = make_item(
properties={
"is_stock_item": 1,
"standard_rate": 100,
"opening_stock": 100,
"last_purchase_rate": 100,
}
).name
rm_item2 = make_item(
properties={
"is_stock_item": 1,
"standard_rate": 200,
"opening_stock": 200,
"last_purchase_rate": 200,
}
).name
return fg_item, [rm_item1, rm_item2]
def get_expected_data(bom, warehouse, qty_to_produce, show_exploded_view=False):
expected_data = []
for item in bom.get("exploded_items") if show_exploded_view else bom.get("items"):
in_stock_qty = None
if frappe.db.exists("Bin", {"item_code": item.item_code, "warehouse": warehouse}, "actual_qty"):
in_stock_qty = frappe.get_cached_value(
"Bin", {"item_code": item.item_code, "warehouse": warehouse}, "actual_qty"
)
expected_data.append(
[
item.item_code,
item.description,
item.stock_qty,
item.stock_uom,
item.stock_qty * qty_to_produce / bom.quantity,
in_stock_qty,
floor(in_stock_qty / (item.stock_qty * qty_to_produce / bom.quantity))
if in_stock_qty
else None,
]
)
return expected_data

View File

@@ -69,7 +69,7 @@ def get_columns(filters):
"label": _("Id"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Work Order",
"options": "Quality Inspection",
"width": 100,
},
{"label": _("Report Date"), "fieldname": "report_date", "fieldtype": "Date", "width": 150},

View File

@@ -376,3 +376,4 @@ erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
execute:frappe.db.set_value("Naming Series", "Naming Series", {"select_doc_for_series": "", "set_options": "", "prefix": "", "current_value": 0, "user_must_always_select": 0})
erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v13_0.update_asset_value_for_manual_depr_entries
erpnext.patches.v13_0.update_docs_link

View File

@@ -1,16 +1,61 @@
import frappe
from erpnext.regional.india.setup import make_custom_fields
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
if frappe.get_all("Company", filters={"country": "India"}):
frappe.reload_doc("accounts", "doctype", "POS Invoice")
frappe.reload_doc("accounts", "doctype", "POS Invoice Item")
make_custom_fields()
custom_fields = get_non_profit_custom_fields()
create_custom_fields(custom_fields, update=True)
if not frappe.db.exists("Party Type", "Donor"):
frappe.get_doc(
{"doctype": "Party Type", "party_type": "Donor", "account_type": "Receivable"}
).insert(ignore_permissions=True)
).insert(ignore_permissions=True, ignore_mandatory=True)
def get_non_profit_custom_fields():
return {
"Company": [
{
"fieldname": "non_profit_section",
"label": "Non Profit Settings",
"fieldtype": "Section Break",
"insert_after": "asset_received_but_not_billed",
"collapsible": 1,
},
{
"fieldname": "company_80g_number",
"label": "80G Number",
"fieldtype": "Data",
"insert_after": "non_profit_section",
},
{
"fieldname": "with_effect_from",
"label": "80G With Effect From",
"fieldtype": "Date",
"insert_after": "company_80g_number",
},
{
"fieldname": "pan_details",
"label": "PAN Number",
"fieldtype": "Data",
"insert_after": "with_effect_from",
},
],
"Member": [
{
"fieldname": "pan_number",
"label": "PAN Details",
"fieldtype": "Data",
"insert_after": "email_id",
},
],
"Donor": [
{
"fieldname": "pan_number",
"label": "PAN Details",
"fieldtype": "Data",
"insert_after": "email",
},
],
}

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
def execute():
navbar_settings = frappe.get_single("Navbar Settings")
for item in navbar_settings.help_dropdown:
if item.is_standard and item.route == "https://erpnext.com/docs/user/manual":
item.route = "https://docs.erpnext.com/docs/v13/user/manual/en/introduction"
navbar_settings.save()

View File

@@ -67,7 +67,6 @@
"label": "Disabled"
},
{
"depends_on": "allow_tax_exemption",
"fieldname": "standard_tax_exemption_amount",
"fieldtype": "Currency",
"label": "Standard Tax Exemption Amount",
@@ -104,7 +103,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2021-03-31 22:42:08.139520",
"modified": "2023-05-01 13:42:08.139520",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Income Tax Slab",

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

@@ -324,6 +324,8 @@ class SalarySlip(TransactionBase):
holidays = self.get_holidays_for_employee(self.start_date, self.end_date)
joining_date, relieving_date = self.get_joining_and_relieving_dates()
if not cint(include_holidays_in_total_working_days):
working_days -= len(holidays)
working_days_list = [cstr(day) for day in working_days_list if cstr(day) not in holidays]
@@ -335,10 +337,14 @@ class SalarySlip(TransactionBase):
frappe.throw(_("Please set Payroll based on in Payroll settings"))
if payroll_based_on == "Attendance":
actual_lwp, absent = self.calculate_lwp_ppl_and_absent_days_based_on_attendance(holidays)
actual_lwp, absent = self.calculate_lwp_ppl_and_absent_days_based_on_attendance(
holidays, relieving_date
)
self.absent_days = absent
else:
actual_lwp = self.calculate_lwp_or_ppl_based_on_leave_application(holidays, working_days_list)
actual_lwp = self.calculate_lwp_or_ppl_based_on_leave_application(
holidays, working_days_list, relieving_date
)
if not lwp:
lwp = actual_lwp
@@ -461,7 +467,10 @@ class SalarySlip(TransactionBase):
def get_holidays_for_employee(self, start_date, end_date):
return get_holiday_dates_for_employee(self.employee, start_date, end_date)
def calculate_lwp_or_ppl_based_on_leave_application(self, holidays, working_days_list):
def calculate_lwp_or_ppl_based_on_leave_application(
self, holidays, working_days_list, relieving_date=None
):
lwp = 0
daily_wages_fraction_for_half_day = (
@@ -469,6 +478,9 @@ class SalarySlip(TransactionBase):
)
for d in working_days_list:
if relieving_date and getdate(d) > getdate(relieving_date):
break
leave = get_lwp_or_ppl_for_date(d, self.employee, holidays)
if leave:
@@ -488,10 +500,15 @@ class SalarySlip(TransactionBase):
return lwp
def calculate_lwp_ppl_and_absent_days_based_on_attendance(self, holidays):
def calculate_lwp_ppl_and_absent_days_based_on_attendance(self, holidays, relieving_date=None):
lwp = 0
absent = 0
end_date = self.end_date
if relieving_date:
end_date = relieving_date
daily_wages_fraction_for_half_day = (
flt(frappe.db.get_value("Payroll Settings", None, "daily_wages_fraction_for_half_day")) or 0.5
)
@@ -506,7 +523,7 @@ class SalarySlip(TransactionBase):
for leave_type in leave_types:
leave_type_map[leave_type.name] = leave_type
attendances = frappe.db.sql(
attendances = frappe.db.sql( # nosemgrep
"""
SELECT attendance_date, status, leave_type
FROM `tabAttendance`
@@ -516,7 +533,7 @@ class SalarySlip(TransactionBase):
AND docstatus = 1
AND attendance_date between %s and %s
""",
values=(self.employee, self.start_date, self.end_date),
values=(self.employee, self.start_date, end_date),
as_dict=1,
)
@@ -636,15 +653,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)
@@ -1314,6 +1333,7 @@ class SalarySlip(TransactionBase):
if declaration:
total_exemption_amount = declaration
if tax_slab.standard_tax_exemption_amount:
total_exemption_amount += flt(tax_slab.standard_tax_exemption_amount)
return total_exemption_amount

View File

@@ -267,7 +267,6 @@ class TestSalarySlip(FrappeTestCase):
make_leave_application(emp_id, first_sunday, add_days(first_sunday, 3), "Leave Without Pay")
leave_type_ppl = create_leave_type(leave_type_name="Test Partially Paid Leave", is_ppl=1)
leave_type_ppl.save()
alloc = create_leave_allocation(
employee=emp_id,
@@ -1128,6 +1127,35 @@ class TestSalarySlip(FrappeTestCase):
if deduction.salary_component == "TDS":
self.assertEqual(deduction.amount, rounded(monthly_tax_amount))
@change_settings("Payroll Settings", {"payroll_based_on": "Leave"})
def test_lwp_calculation_based_on_relieving_date(self):
emp_id = make_employee("test_lwp_based_on_relieving_date@salary.com")
frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
month_start_date = get_first_day(nowdate())
first_sunday = get_first_sunday(for_date=month_start_date)
relieving_date = add_days(first_sunday, 10)
leave_start_date = add_days(first_sunday, 16)
leave_end_date = add_days(leave_start_date, 2)
make_leave_application(emp_id, leave_start_date, leave_end_date, "Leave Without Pay")
frappe.db.set_value("Employee", emp_id, {"relieving_date": relieving_date, "status": "Left"})
ss = make_employee_salary_slip(
"test_lwp_based_on_relieving_date@salary.com",
"Monthly",
"Test Payment Based On Leave Application",
)
holidays = ss.get_holidays_for_employee(month_start_date, relieving_date)
days_between_start_and_relieving = date_diff(relieving_date, month_start_date) + 1
self.assertEqual(ss.leave_without_pay, 0)
self.assertEqual(ss.payment_days, (days_between_start_and_relieving - len(holidays)))
def get_no_of_days():
no_of_days_in_month = calendar.monthrange(getdate(nowdate()).year, getdate(nowdate()).month)
@@ -1587,9 +1615,8 @@ def setup_test():
frappe.db.set_value("HR Settings", None, "leave_approval_notification_template", None)
def make_holiday_list(list_name=None, from_date=None, to_date=None):
if not (from_date and to_date):
fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
def make_holiday_list(list_name=None, from_date=None, to_date=None, add_weekly_offs=True):
fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
name = list_name or "Salary Slip Test Holiday List"
frappe.delete_doc_if_exists("Holiday List", name, force=True)
@@ -1600,10 +1627,13 @@ def make_holiday_list(list_name=None, from_date=None, to_date=None):
"holiday_list_name": name,
"from_date": from_date or fiscal_year[1],
"to_date": to_date or fiscal_year[2],
"weekly_off": "Sunday",
}
).insert()
holiday_list.get_weekly_off_dates()
if add_weekly_offs:
holiday_list.weekly_off = "Sunday"
holiday_list.get_weekly_off_dates()
holiday_list.save()
holiday_list = holiday_list.name

View File

@@ -8,222 +8,325 @@ from frappe.utils import flt
import erpnext
salary_slip = frappe.qb.DocType("Salary Slip")
salary_detail = frappe.qb.DocType("Salary Detail")
salary_component = frappe.qb.DocType("Salary Component")
def execute(filters=None):
if not filters:
filters = {}
currency = None
if filters.get("currency"):
currency = filters.get("currency")
company_currency = erpnext.get_company_currency(filters.get("company"))
salary_slips = get_salary_slips(filters, company_currency)
if not salary_slips:
return [], []
columns, earning_types, ded_types = get_columns(salary_slips)
ss_earning_map = get_ss_earning_map(salary_slips, currency, company_currency)
ss_ded_map = get_ss_ded_map(salary_slips, currency, company_currency)
earning_types, ded_types = get_earning_and_deduction_types(salary_slips)
columns = get_columns(earning_types, ded_types)
ss_earning_map = get_salary_slip_details(salary_slips, currency, company_currency, "earnings")
ss_ded_map = get_salary_slip_details(salary_slips, currency, company_currency, "deductions")
doj_map = get_employee_doj_map()
data = []
for ss in salary_slips:
row = [
ss.name,
ss.employee,
ss.employee_name,
doj_map.get(ss.employee),
ss.branch,
ss.department,
ss.designation,
ss.company,
ss.start_date,
ss.end_date,
ss.leave_without_pay,
ss.payment_days,
]
row = {
"salary_slip_id": ss.name,
"employee": ss.employee,
"employee_name": ss.employee_name,
"data_of_joining": doj_map.get(ss.employee),
"branch": ss.branch,
"department": ss.department,
"designation": ss.designation,
"company": ss.company,
"start_date": ss.start_date,
"end_date": ss.end_date,
"leave_without_pay": ss.leave_without_pay,
"payment_days": ss.payment_days,
"currency": currency or company_currency,
"total_loan_repayment": ss.total_loan_repayment,
}
if ss.branch is not None:
columns[3] = columns[3].replace("-1", "120")
if ss.department is not None:
columns[4] = columns[4].replace("-1", "120")
if ss.designation is not None:
columns[5] = columns[5].replace("-1", "120")
if ss.leave_without_pay is not None:
columns[9] = columns[9].replace("-1", "130")
update_column_width(ss, columns)
for e in earning_types:
row.append(ss_earning_map.get(ss.name, {}).get(e))
if currency == company_currency:
row += [flt(ss.gross_pay) * flt(ss.exchange_rate)]
else:
row += [ss.gross_pay]
row.update({frappe.scrub(e): ss_earning_map.get(ss.name, {}).get(e)})
for d in ded_types:
row.append(ss_ded_map.get(ss.name, {}).get(d))
row.append(ss.total_loan_repayment)
row.update({frappe.scrub(d): ss_ded_map.get(ss.name, {}).get(d)})
if currency == company_currency:
row += [
flt(ss.total_deduction) * flt(ss.exchange_rate),
flt(ss.net_pay) * flt(ss.exchange_rate),
]
row.update(
{
"gross_pay": flt(ss.gross_pay) * flt(ss.exchange_rate),
"total_deduction": flt(ss.total_deduction) * flt(ss.exchange_rate),
"net_pay": flt(ss.net_pay) * flt(ss.exchange_rate),
}
)
else:
row += [ss.total_deduction, ss.net_pay]
row.append(currency or company_currency)
row.update(
{"gross_pay": ss.gross_pay, "total_deduction": ss.total_deduction, "net_pay": ss.net_pay}
)
data.append(row)
return columns, data
def get_columns(salary_slips):
"""
def get_earning_and_deduction_types(salary_slips):
salary_component_and_type = {_("Earning"): [], _("Deduction"): []}
for salary_compoent in get_salary_components(salary_slips):
component_type = get_salary_component_type(salary_compoent[0])
salary_component_and_type[_(component_type)].append(salary_compoent[0])
return sorted(salary_component_and_type[_("Earning")]), sorted(
salary_component_and_type[_("Deduction")]
)
def update_column_width(ss, columns):
if ss.branch is not None:
columns[3].update({"width": 120})
if ss.department is not None:
columns[4].update({"width": 120})
if ss.designation is not None:
columns[5].update({"width": 120})
if ss.leave_without_pay is not None:
columns[9].update({"width": 120})
def get_columns(earning_types, ded_types):
columns = [
_("Salary Slip ID") + ":Link/Salary Slip:150",
_("Employee") + ":Link/Employee:120",
_("Employee Name") + "::140",
_("Date of Joining") + "::80",
_("Branch") + ":Link/Branch:120",
_("Department") + ":Link/Department:120",
_("Designation") + ":Link/Designation:120",
_("Company") + ":Link/Company:120",
_("Start Date") + "::80",
_("End Date") + "::80",
_("Leave Without Pay") + ":Float:130",
_("Payment Days") + ":Float:120",
_("Currency") + ":Link/Currency:80"
]
"""
columns = [
_("Salary Slip ID") + ":Link/Salary Slip:150",
_("Employee") + ":Link/Employee:120",
_("Employee Name") + "::140",
_("Date of Joining") + "::80",
_("Branch") + ":Link/Branch:-1",
_("Department") + ":Link/Department:-1",
_("Designation") + ":Link/Designation:120",
_("Company") + ":Link/Company:120",
_("Start Date") + "::80",
_("End Date") + "::80",
_("Leave Without Pay") + ":Float:50",
_("Payment Days") + ":Float:120",
{
"label": _("Salary Slip ID"),
"fieldname": "salary_slip_id",
"fieldtype": "Link",
"options": "Salary Slip",
"width": 150,
},
{
"label": _("Employee"),
"fieldname": "employee",
"fieldtype": "Link",
"options": "Employee",
"width": 120,
},
{
"label": _("Employee Name"),
"fieldname": "employee_name",
"fieldtype": "Data",
"width": 140,
},
{
"label": _("Date of Joining"),
"fieldname": "data_of_joining",
"fieldtype": "Date",
"width": 80,
},
{
"label": _("Branch"),
"fieldname": "branch",
"fieldtype": "Link",
"options": "Branch",
"width": -1,
},
{
"label": _("Department"),
"fieldname": "department",
"fieldtype": "Link",
"options": "Department",
"width": -1,
},
{
"label": _("Designation"),
"fieldname": "designation",
"fieldtype": "Link",
"options": "Designation",
"width": 120,
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
"width": 120,
},
{
"label": _("Start Date"),
"fieldname": "start_date",
"fieldtype": "Data",
"width": 80,
},
{
"label": _("End Date"),
"fieldname": "end_date",
"fieldtype": "Data",
"width": 80,
},
{
"label": _("Leave Without Pay"),
"fieldname": "leave_without_pay",
"fieldtype": "Float",
"width": 50,
},
{
"label": _("Payment Days"),
"fieldname": "payment_days",
"fieldtype": "Float",
"width": 120,
},
{
"label": _("Currency"),
"fieldname": "currency",
"fieldtype": "Link",
"options": "Currency",
"hidden": 1,
},
]
salary_components = {_("Earning"): [], _("Deduction"): []}
for earning in earning_types:
columns.append(
{
"label": earning,
"fieldname": frappe.scrub(earning),
"fieldtype": "Currency",
"options": "currency",
"width": 120,
}
)
for component in frappe.db.sql(
"""select distinct sd.salary_component, sc.type
from `tabSalary Detail` sd, `tabSalary Component` sc
where sc.name=sd.salary_component and sd.amount != 0 and sd.parent in (%s)"""
% (", ".join(["%s"] * len(salary_slips))),
tuple([d.name for d in salary_slips]),
as_dict=1,
):
salary_components[_(component.type)].append(component.salary_component)
columns.append(
{
"label": _("Gross Pay"),
"fieldname": "gross_pay",
"fieldtype": "Currency",
"options": "currency",
"width": 120,
}
)
columns = (
columns
+ [(e + ":Currency:120") for e in salary_components[_("Earning")]]
+ [_("Gross Pay") + ":Currency:120"]
+ [(d + ":Currency:120") for d in salary_components[_("Deduction")]]
+ [
_("Loan Repayment") + ":Currency:120",
_("Total Deduction") + ":Currency:120",
_("Net Pay") + ":Currency:120",
for deduction in ded_types:
columns.append(
{
"label": deduction,
"fieldname": frappe.scrub(deduction),
"fieldtype": "Currency",
"options": "currency",
"width": 120,
}
)
columns.extend(
[
{
"label": _("Loan Repayment"),
"fieldname": "total_loan_repayment",
"fieldtype": "Currency",
"options": "currency",
"width": 120,
},
{
"label": _("Total Deduction"),
"fieldname": "total_deduction",
"fieldtype": "Currency",
"options": "currency",
"width": 120,
},
{
"label": _("Net Pay"),
"fieldname": "net_pay",
"fieldtype": "Currency",
"options": "currency",
"width": 120,
},
]
)
return columns, salary_components[_("Earning")], salary_components[_("Deduction")]
return columns
def get_salary_components(salary_slips):
return (
frappe.qb.from_(salary_detail)
.where((salary_detail.amount != 0) & (salary_detail.parent.isin([d.name for d in salary_slips])))
.select(salary_detail.salary_component)
.distinct()
).run(as_list=True)
def get_salary_component_type(salary_component):
return frappe.db.get_value("Salary Component", salary_component, "type", cache=True)
def get_salary_slips(filters, company_currency):
filters.update({"from_date": filters.get("from_date"), "to_date": filters.get("to_date")})
conditions, filters = get_conditions(filters, company_currency)
salary_slips = frappe.db.sql(
"""select * from `tabSalary Slip` where %s
order by employee"""
% conditions,
filters,
as_dict=1,
)
doc_status = {"Draft": 0, "Submitted": 1, "Cancelled": 2}
query = frappe.qb.from_(salary_slip).select(salary_slip.star)
if filters.get("docstatus"):
query = query.where(salary_slip.docstatus == doc_status[filters.get("docstatus")])
if filters.get("from_date"):
query = query.where(salary_slip.start_date >= filters.get("from_date"))
if filters.get("to_date"):
query = query.where(salary_slip.end_date <= filters.get("to_date"))
if filters.get("company"):
query = query.where(salary_slip.company == filters.get("company"))
if filters.get("employee"):
query = query.where(salary_slip.employee == filters.get("employee"))
if filters.get("currency") and filters.get("currency") != company_currency:
query = query.where(salary_slip.currency == filters.get("currency"))
salary_slips = query.run(as_dict=1)
return salary_slips or []
def get_conditions(filters, company_currency):
conditions = ""
doc_status = {"Draft": 0, "Submitted": 1, "Cancelled": 2}
if filters.get("docstatus"):
conditions += "docstatus = {0}".format(doc_status[filters.get("docstatus")])
if filters.get("from_date"):
conditions += " and start_date >= %(from_date)s"
if filters.get("to_date"):
conditions += " and end_date <= %(to_date)s"
if filters.get("company"):
conditions += " and company = %(company)s"
if filters.get("employee"):
conditions += " and employee = %(employee)s"
if filters.get("currency") and filters.get("currency") != company_currency:
conditions += " and currency = %(currency)s"
return conditions, filters
def get_employee_doj_map():
return frappe._dict(
frappe.db.sql(
"""
SELECT
employee,
date_of_joining
FROM `tabEmployee`
"""
employee = frappe.qb.DocType("Employee")
result = (frappe.qb.from_(employee).select(employee.name, employee.date_of_joining)).run()
return frappe._dict(result)
def get_salary_slip_details(salary_slips, currency, company_currency, component_type):
salary_slips = [ss.name for ss in salary_slips]
result = (
frappe.qb.from_(salary_slip)
.join(salary_detail)
.on(salary_slip.name == salary_detail.parent)
.where((salary_detail.parent.isin(salary_slips)) & (salary_detail.parentfield == component_type))
.select(
salary_detail.parent,
salary_detail.salary_component,
salary_detail.amount,
salary_slip.exchange_rate,
)
)
).run(as_dict=1)
ss_map = {}
def get_ss_earning_map(salary_slips, currency, company_currency):
ss_earnings = frappe.db.sql(
"""select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name
from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)"""
% (", ".join(["%s"] * len(salary_slips))),
tuple([d.name for d in salary_slips]),
as_dict=1,
)
ss_earning_map = {}
for d in ss_earnings:
ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0)
for d in result:
ss_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0)
if currency == company_currency:
ss_earning_map[d.parent][d.salary_component] += flt(d.amount) * flt(
ss_map[d.parent][d.salary_component] += flt(d.amount) * flt(
d.exchange_rate if d.exchange_rate else 1
)
else:
ss_earning_map[d.parent][d.salary_component] += flt(d.amount)
ss_map[d.parent][d.salary_component] += flt(d.amount)
return ss_earning_map
def get_ss_ded_map(salary_slips, currency, company_currency):
ss_deductions = frappe.db.sql(
"""select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name
from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)"""
% (", ".join(["%s"] * len(salary_slips))),
tuple([d.name for d in salary_slips]),
as_dict=1,
)
ss_ded_map = {}
for d in ss_deductions:
ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0)
if currency == company_currency:
ss_ded_map[d.parent][d.salary_component] += flt(d.amount) * flt(
d.exchange_rate if d.exchange_rate else 1
)
else:
ss_ded_map[d.parent][d.salary_component] += flt(d.amount)
return ss_ded_map
return ss_map

View File

@@ -124,8 +124,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
}
else {
let qty = item.qty || 1;
qty = me.frm.doc.is_return ? -1 * qty : qty;
// allow for '0' qty on Credit/Debit notes
let qty = item.qty || (me.frm.doc.is_debit_note ? 1 : -1);
item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item));
}

View File

@@ -297,8 +297,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
});
},
make_payment_request: function() {
var me = this;
make_payment_request() {
let me = this;
const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype))
? "Inward" : "Outward";
@@ -314,7 +314,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
},
callback: function(r) {
if(!r.exc){
var doc = frappe.model.sync(r.message);
frappe.model.sync(r.message);
frappe.set_route("Form", r.message.doctype, r.message.name);
}
}
@@ -1975,32 +1975,74 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
get_advances: function() {
if(!this.frm.is_return) {
var me = this;
return this.frm.call({
method: "set_advances",
doc: this.frm.doc,
callback: function(r, rt) {
refresh_field("advances");
me.frm.dirty();
}
})
}
},
make_payment_entry: function() {
make_payment_entry() {
let via_journal_entry = this.frm.doc.__onload && this.frm.doc.__onload.make_payment_via_journal_entry;
if(this.has_discount_in_schedule() && !via_journal_entry) {
// If early payment discount is applied, ask user for reference date
this.prompt_user_for_reference_date();
} else {
this.make_mapped_payment_entry();
}
},
make_mapped_payment_entry(args) {
var me = this;
args = args || { "dt": this.frm.doc.doctype, "dn": this.frm.doc.name };
return frappe.call({
method: cur_frm.cscript.get_method_for_payment(),
args: {
"dt": cur_frm.doc.doctype,
"dn": cur_frm.doc.name
},
method: me.get_method_for_payment(),
args: args,
callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
// cur_frm.refresh_fields()
}
});
},
make_quality_inspection: function () {
prompt_user_for_reference_date(){
let me = this;
frappe.prompt({
label: __("Cheque/Reference Date"),
fieldname: "reference_date",
fieldtype: "Date",
reqd: 1,
}, (values) => {
let args = {
"dt": me.frm.doc.doctype,
"dn": me.frm.doc.name,
"reference_date": values.reference_date
}
me.make_mapped_payment_entry(args);
},
__("Reference Date for Early Payment Discount"),
__("Continue")
);
},
has_discount_in_schedule() {
let is_eligible = in_list(
["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"],
this.frm.doctype
);
let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length;
if(!is_eligible || !has_payment_schedule) return false;
let has_discount = this.frm.doc.payment_schedule.some(row => row.discount);
return has_discount;
},
make_quality_inspection() {
let data = [];
const fields = [
{

View File

@@ -3,18 +3,6 @@
if(!window.erpnext) window.erpnext = {};
// Add / update a new Lead / Communication
// subject, sender, description
frappe.send_message = function(opts, btn) {
return frappe.call({
type: "POST",
method: "erpnext.templates.utils.send_message",
btn: btn,
args: opts,
callback: opts.callback
});
};
erpnext.subscribe_to_newsletter = function(opts, btn) {
return frappe.call({
type: "POST",
@@ -24,6 +12,3 @@ erpnext.subscribe_to_newsletter = function(opts, btn) {
callback: opts.callback
});
}
// for backward compatibility
erpnext.send_message = frappe.send_message;

View File

@@ -17,6 +17,10 @@ from frappe.utils import (
)
from six import string_types
from erpnext.assets.doctype.asset.asset import (
get_straight_line_or_manual_depr_amount,
get_wdv_or_dd_depr_amount,
)
from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
from erpnext.hr.utils import get_salary_assignments
@@ -1099,20 +1103,16 @@ def update_taxable_values(doc, method):
doc.get("items")[item_count - 1].taxable_value += diff
def get_depreciation_amount(asset, depreciable_value, row):
def get_depreciation_amount(
asset,
depreciable_value,
row,
schedule_idx=0,
prev_depreciation_amount=0,
has_wdv_or_dd_non_yearly_pro_rata=False,
):
if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life:
depreciation_amount = (
flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations)
# if the Depreciation Schedule is being modified after Asset Repair
else:
depreciation_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
return get_straight_line_or_manual_depr_amount(asset, row)
else:
rate_of_depreciation = row.rate_of_depreciation
# if its the first depreciation
@@ -1127,10 +1127,14 @@ def get_depreciation_amount(asset, depreciable_value, row):
"As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%."
)
)
depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100))
return depreciation_amount
return get_wdv_or_dd_depr_amount(
depreciable_value,
rate_of_depreciation,
row.frequency_of_depreciation,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
)
def set_item_tax_from_hsn_code(item):

View File

@@ -123,7 +123,7 @@ frappe.ui.form.on("Customer", {
frm.add_custom_button(__('Accounting Ledger'), function () {
frappe.set_route('query-report', 'General Ledger',
{party_type: 'Customer', party: frm.doc.name});
{party_type: 'Customer', party: frm.doc.name, party_name: frm.doc.customer_name});
}, __('View'));
frm.add_custom_button(__('Pricing Rule'), function () {

View File

@@ -275,18 +275,9 @@ class Customer(TransactionBase):
def on_trash(self):
if self.customer_primary_contact:
frappe.db.sql(
"""
UPDATE `tabCustomer`
SET
customer_primary_contact=null,
customer_primary_address=null,
mobile_no=null,
email_id=null,
primary_address=null
WHERE name=%(name)s""",
{"name": self.name},
)
self.db_set("customer_primary_contact", None)
if self.customer_primary_address:
self.db_set("customer_primary_address", None)
delete_contact_and_address("Customer", self.name)
if self.lead_name:

View File

@@ -3,6 +3,7 @@
import frappe
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.test_runner import make_test_records
from frappe.tests.utils import FrappeTestCase
from frappe.utils import flt
@@ -343,6 +344,37 @@ class TestCustomer(FrappeTestCase):
due_date = get_due_date("2017-01-22", "Customer", "_Test Customer")
self.assertEqual(due_date, "2017-01-22")
def test_serach_fields_for_customer(self):
from erpnext.controllers.queries import customer_query
frappe.db.set_value("Selling Settings", None, "cust_master_name", "Naming Series")
make_property_setter(
"Customer", None, "search_fields", "customer_group", "Data", for_doctype="Doctype"
)
data = customer_query(
"Customer", "_Test Customer", "", 0, 20, filters={"name": "_Test Customer"}, as_dict=True
)
self.assertEqual(data[0].name, "_Test Customer")
self.assertEqual(data[0].customer_group, "_Test Customer Group")
self.assertTrue("territory" not in data[0])
make_property_setter(
"Customer", None, "search_fields", "customer_group, territory", "Data", for_doctype="Doctype"
)
data = customer_query(
"Customer", "_Test Customer", "", 0, 20, filters={"name": "_Test Customer"}, as_dict=True
)
self.assertEqual(data[0].name, "_Test Customer")
self.assertEqual(data[0].customer_group, "_Test Customer Group")
self.assertEqual(data[0].territory, "_Test Territory")
self.assertTrue("territory" in data[0])
frappe.db.set_value("Selling Settings", None, "cust_master_name", "Customer Name")
def get_customer_dict(customer_name):
return {

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