Compare commits

..

310 Commits

Author SHA1 Message Date
Frappe PR Bot
6faf459f97 chore(release): Bumped to Version 14.22.1
## [14.22.1](https://github.com/frappe/erpnext/compare/v14.22.0...v14.22.1) (2023-04-20)

### Bug Fixes

* add limit for get_next_stock_reco ([#34937](https://github.com/frappe/erpnext/issues/34937)) ([78ad0ea](78ad0eaa74))
2023-04-20 05:12:57 +00:00
Deepesh Garg
40058c2617 Merge pull request #34939 from frappe/mergify/bp/version-14/pr-34938
fix: add limit for get_next_stock_reco (#34937)
2023-04-20 10:41:31 +05:30
mergify[bot]
78ad0eaa74 fix: add limit for get_next_stock_reco (#34937)
fix: limit stock reco issue

(cherry picked from commit fcfa8842a7)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
(cherry picked from commit 7131ff28fd)
2023-04-20 05:08:45 +00:00
Frappe PR Bot
b12bd5f558 chore(release): Bumped to Version 14.22.0
# [14.22.0](https://github.com/frappe/erpnext/compare/v14.21.0...v14.22.0) (2023-04-19)

### Bug Fixes

* Add offers info to website item ([#34873](https://github.com/frappe/erpnext/issues/34873)) ([f1a1fc6](f1a1fc6c5b))
* Advance payment against payment terms ([#34872](https://github.com/frappe/erpnext/issues/34872)) ([7461239](7461239218))
* change discuss forum url ([#34891](https://github.com/frappe/erpnext/issues/34891)) ([8f26d62](8f26d62b35))
* don't show disabled warehouses in the Warehouse Wise Stock Balance report ([461780d](461780da22))
* Don't use stale item details ([#34847](https://github.com/frappe/erpnext/issues/34847)) ([c11aeba](c11aebaaae))
* for Tree Type item and item group show net amount ([#31776](https://github.com/frappe/erpnext/issues/31776)) ([7c8194a](7c8194a1a8))
* linters issues ([c53dc06](c53dc06f80))
* Remove unnecessary checkbox from Accounts doctype ([#34821](https://github.com/frappe/erpnext/issues/34821)) ([0cf6144](0cf6144b3f))
* selling workspace is not migrating properly ([79fd38c](79fd38cf3f))
* stock reco test case ([2f356dc](2f356dcc6c))
* too many writes error while making backdated stock reconciliation ([a981b79](a981b79865))
* unable to change `company` for manual `Serial No` entry ([2e7043c](2e7043ca90))
* **ux:** don't throw error when company defaults aren't set ([#34825](https://github.com/frappe/erpnext/issues/34825)) ([15f5e8d](15f5e8d4ff))
* whitelist doc method ([09b92fd](09b92fd78c))

### Features

* add german sales tax template ([#34823](https://github.com/frappe/erpnext/issues/34823)) ([3738ea5](3738ea5794))
2023-04-19 01:36:11 +00:00
Deepesh Garg
be8a22d33b Merge pull request #34905 from frappe/version-14-hotfix
chore: release v14
2023-04-19 07:04:25 +05:30
Sagar Sharma
757c168a8d Merge pull request #34903 from frappe/mergify/bp/version-14-hotfix/pr-34860
fix: add items field label (backport #34860)
2023-04-18 14:22:47 +05:30
s-aga-r
b0b00dc869 chore: add items field label
(cherry picked from commit c9418aab45)
2023-04-18 07:35:01 +00:00
Ankush Menat
09b92fd78c fix: whitelist doc method
This should've been whitelisted, looks like it was missed out

closes https://github.com/frappe/erpnext/issues/34898

(cherry picked from commit e4f152a416)
2023-04-18 08:25:26 +05:30
mergify[bot]
f1a1fc6c5b fix: Add offers info to website item (#34873)
fix: Add offers info to website item (#34873)

* fix: Add offers info to website item

* Revert "fix: Add offers info to website item"

This reverts commit 88b598edb6.

* fix: Add offer properties to website item

(cherry picked from commit 534ea5ad21)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-18 07:43:17 +05:30
mergify[bot]
8f26d62b35 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:45 +05:30
rohitwaghchaure
8e9d72bd2e Merge pull request #34887 from frappe/mergify/bp/version-14-hotfix/pr-34886
fix: stock reconciliation test case (backport #34886)
2023-04-17 16:02:15 +05:30
mergify[bot]
7461239218 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

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-17 15:54:54 +05:30
Rohit Waghchaure
2f356dcc6c fix: stock reco test case
(cherry picked from commit 6bccd8644e)
2023-04-17 09:52:59 +00:00
rohitwaghchaure
4640febc83 Merge pull request #34884 from frappe/mergify/bp/version-14-hotfix/pr-34851
fix: too many writes error while making backdated stock reconciliation (backport #34851)
2023-04-17 13:48:25 +05:30
Rohit Waghchaure
c53dc06f80 fix: linters issues
(cherry picked from commit d9dd64b4d2)
2023-04-17 07:13:00 +00:00
Rohit Waghchaure
a981b79865 fix: too many writes error while making backdated stock reconciliation
(cherry picked from commit 7bfc8f1236)
2023-04-17 07:13:00 +00:00
rohitwaghchaure
050339c88e Merge pull request #34883 from frappe/mergify/bp/version-14-hotfix/pr-34882
fix: don't show disabled warehouses in the Warehouse Wise Stock Balance report (backport #34882)
2023-04-17 12:41:25 +05:30
Rohit Waghchaure
461780da22 fix: don't show disabled warehouses in the Warehouse Wise Stock Balance report
(cherry picked from commit 9ceb1f6bda)
2023-04-17 06:31:44 +00:00
Shariq Ansari
e8db543028 Merge pull request #34877 from frappe/mergify/bp/version-14-hotfix/pr-34876
fix: selling workspace is not migrating properly (backport #34876)
2023-04-16 14:30:23 +05:30
Shariq Ansari
79fd38cf3f fix: selling workspace is not migrating properly
(cherry picked from commit 5a4dd354c1)
2023-04-16 08:57:11 +00:00
Sagar Sharma
dfb1e105bd Merge pull request #34868 from frappe/mergify/bp/version-14-hotfix/pr-34858
fix: unable to change `company` for manual `Serial No` entry (backport #34858)
2023-04-15 12:45:29 +05:30
s-aga-r
2e7043ca90 fix: unable to change company for manual Serial No entry
(cherry picked from commit fb3271c624)
2023-04-15 06:41:00 +00:00
mergify[bot]
7c8194a1a8 fix: for Tree Type item and item group show net amount (#31776)
fix: for Tree Type item and item group show net amout

(cherry picked from commit 91762097a5)

Co-authored-by: hrzzz <paulo_fabris@hotmail.com>
2023-04-14 16:42:37 +05:30
mergify[bot]
15f5e8d4ff fix(ux): don't throw error when company defaults aren't set (#34825)
fix(ux): don't throw error when company defaults aren't set (#34825)

* fix(ux): don't throw error when company defaults aren't set; instead prompt account input.

* fix: translate label and title

(cherry picked from commit 51c4338661)

Co-authored-by: Devin Slauenwhite <devin.slauenwhite@gmail.com>
2023-04-14 16:42:12 +05:30
mergify[bot]
3738ea5794 feat: add german sales tax template (#34823)
feat: add german sales tax template (#34823)

Nullsteuersatz nach § 12 Abs. 3 UStG

(cherry picked from commit 59f6b773cd)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-04-14 14:52:02 +05:30
mergify[bot]
c11aebaaae fix: Don't use stale item details (#34847)
fix: Don't use stale item details (#34847)

(cherry picked from commit a7051cb9b5)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-14 11:26:33 +05:30
mergify[bot]
0cf6144b3f fix: Remove unnecessary checkbox from Accounts doctype (#34821)
fix: Remove unnecessary checkbox from Accounts doctype (#34821)

(cherry picked from commit 66130493eb)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-14 11:26:14 +05:30
mergify[bot]
41a7d3fd60 chore: update CODEOWNERS (#34817)
* chore: update CODEOWNERS

[skip ci]

(cherry picked from commit aa8b241d5a)

# Conflicts:
#	CODEOWNERS

* fix: conflicts

---------

Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
2023-04-12 07:39:07 +05:30
Frappe PR Bot
2b0b24f7c3 chore(release): Bumped to Version 14.21.0
# [14.21.0](https://github.com/frappe/erpnext/compare/v14.20.3...v14.21.0) (2023-04-11)

### Bug Fixes

* `payment entry is already created` on posawesome. (backport [#34712](https://github.com/frappe/erpnext/issues/34712)) ([#34752](https://github.com/frappe/erpnext/issues/34752)) ([8ba1e0f](8ba1e0f31e))
* add german translation of "Partly Paid" ([#34776](https://github.com/frappe/erpnext/issues/34776)) ([3023dbb](3023dbbe95))
* Allocate tax loss to tax account head on early payment discount ([#34287](https://github.com/frappe/erpnext/issues/34287)) ([be2990e](be2990ec88))
* asset monthly WDV and DD schedule [v14] ([#34644](https://github.com/frappe/erpnext/issues/34644)) ([88c8c36](88c8c36805))
* Bank clearance for case loan (disburstment/repayment) ([#34586](https://github.com/frappe/erpnext/issues/34586)) ([f1687cf](f1687cfb14))
* BOM Update Cost, when no actual qty ([8757435](8757435898))
* bom update log not working for large batch size ([551190a](551190af30))
* Column value mismatch in COA blank template ([#34658](https://github.com/frappe/erpnext/issues/34658)) ([5e03a4e](5e03a4e9e2))
* consider qty field precision ([2c54e76](2c54e763e4))
* customer selection not mandatory in purchase invoice to fetch item details ([#34810](https://github.com/frappe/erpnext/issues/34810)) ([994272b](994272b966))
* don't include cancelled JVs in assdeprledger report ([3896d41](3896d41e95))
* enclose ternary operator in parentheses ([b835760](b835760b0b))
* incorrect arg name in asset value adjustment ([8d9305e](8d9305ee5f))
* incorrect balance qty in the stock ledger report ([3494c9c](3494c9ccb6))
* incorrect stock balance quantity for batch item ([d817c50](d817c50581))
* Item tax validity comparison fixes ([#34784](https://github.com/frappe/erpnext/issues/34784)) ([cc21241](cc21241887))
* lost opportunity report issue ([#34626](https://github.com/frappe/erpnext/issues/34626)) ([3e67994](3e67994cc7))
* Multiple issues in purchase invoice submission ([#34600](https://github.com/frappe/erpnext/issues/34600)) ([5677f25](5677f25215))
* plaid log_error syntax issue (backport [#34642](https://github.com/frappe/erpnext/issues/34642)) ([#34667](https://github.com/frappe/erpnext/issues/34667)) ([61858a6](61858a60c2))
* posting time issue ([bb5eeb6](bb5eeb6bd6))
* provide filter by depreciable assets in fixed asset register ([#34803](https://github.com/frappe/erpnext/issues/34803)) ([fee4cd5](fee4cd5f40))
* reposting record not created for backdated stock reco ([9b90323](9b90323d53))
* serial no with zero quantity issue in stock reco ([f47be46](f47be46717))
* Shop by category fixes (backport [#34688](https://github.com/frappe/erpnext/issues/34688)) ([#34750](https://github.com/frappe/erpnext/issues/34750)) ([3ad5d67](3ad5d676ab))
* Subcontracting Receipt incorrect `status` ([99226d3](99226d3811))
* Supplier RFQ email link ([#34338](https://github.com/frappe/erpnext/issues/34338)) ([a00459a](a00459aec3))
* Total debit and credit while importing via Data Import ([#34659](https://github.com/frappe/erpnext/issues/34659)) ([5e28d02](5e28d0234e))
* **ui:** recalculate difference amount on allocation change ([#34694](https://github.com/frappe/erpnext/issues/34694)) ([6b866e2](6b866e24f6))
* Unable to create payment request against purchase invoice ([#34762](https://github.com/frappe/erpnext/issues/34762)) ([a1f7e35](a1f7e35914))
* use stock qty to calculate POS reserved stock ([c0f7f7d](c0f7f7da42))
* UX for stock entry, bom and work order ([d4a6035](d4a6035c83))

### Features

* add `Received Qty` field in `Delivery Note Item` ([1c5e36c](1c5e36c7b6))
* Auto allocate advance payments only against orders ([#34727](https://github.com/frappe/erpnext/issues/34727)) ([05d24e3](05d24e3665))

### Reverts

* remove frappe.send_message (v14) ([#34816](https://github.com/frappe/erpnext/issues/34816)) ([8a331e0](8a331e0f26))
2023-04-11 11:40:59 +00:00
Deepesh Garg
f72fc73913 Merge pull request #34813 from frappe/version-14-hotfix
chore: release v14
2023-04-11 17:09:31 +05:30
Ritwik Puri
8a331e0f26 revert: remove frappe.send_message (v14) (#34816)
revert: remove frappe.send_message
2023-04-11 16:10:32 +05:30
Deepesh Garg
da913d49a7 Merge branch 'version-14' into version-14-hotfix 2023-04-11 15:56:58 +05:30
rohitwaghchaure
21aea52c32 Merge pull request #34814 from frappe/mergify/bp/version-14-hotfix/pr-34808
fix: reposting record not created for backdated stock reconciliation  (backport #34808)
2023-04-11 15:36:32 +05:30
Rohit Waghchaure
9b90323d53 fix: reposting record not created for backdated stock reco
(cherry picked from commit 6851b5ba97)
2023-04-11 09:33:26 +00:00
Deepesh Garg
994272b966 fix: customer selection not mandatory in purchase invoice to fetch item details (#34810) 2023-04-11 14:17:27 +05:30
mergify[bot]
fee4cd5f40 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:04 +05:30
mergify[bot]
3023dbbe95 fix: add german translation of "Partly Paid" (#34776)
fix: add german translation of "Partly Paid" (#34776)

(cherry picked from commit 934e1b4e6a)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-04-09 20:13:36 +05:30
mergify[bot]
cc21241887 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-09 18:55:11 +05:30
Sagar Sharma
66fdd1c13f Merge pull request #34786 from frappe/mergify/bp/version-14-hotfix/pr-34632
refactor: rewrite `batch.py` queries in `QB` (backport #34632)
2023-04-08 22:10:22 +05:30
s-aga-r
5723a200c5 chore: conflicts 2023-04-08 19:10:39 +05:30
s-aga-r
35c9493336 refactor: rewrite batch.py queries in QB
(cherry picked from commit 517b5f8567)

# Conflicts:
#	erpnext/stock/doctype/batch/batch.py
2023-04-08 07:41:49 +00:00
Anand Baburajan
b235b95bed Merge pull request #34779 from frappe/mergify/bp/version-14-hotfix/pr-34735
'Make Asset Movement' button translation fix in asset_list.js (backport #34735)
2023-04-07 15:34:20 +05:30
Hossein Yousefian
50abbded34 'Make Asset Movement' button translation fix
(cherry picked from commit b70615ef18)
2023-04-07 10:01:19 +00:00
Sagar Sharma
31b479d71f Merge pull request #34772 from frappe/mergify/bp/version-14-hotfix/pr-34760
fix: validate `Received Qty` for Internal Purchase Receipt (backport #34760)
2023-04-06 17:20:46 +05:30
s-aga-r
769736ffea test: add test cases for internal PR received qty
(cherry picked from commit a575bd50ef)
2023-04-06 10:56:11 +00:00
s-aga-r
b79ddbbf60 chore: add Delivery Note Item in Purchase Receipt Status Updater
(cherry picked from commit 0d1df26b88)
2023-04-06 10:56:11 +00:00
s-aga-r
1c5e36c7b6 feat: add Received Qty field in Delivery Note Item
(cherry picked from commit bc39dfab5d)
2023-04-06 10:56:11 +00:00
Sagar Sharma
eaf577f078 Merge pull request #34770 from frappe/mergify/bp/version-14-hotfix/pr-34769
fix: Subcontracting Receipt incorrect `status` (backport #34769)
2023-04-06 14:53:34 +05:30
rohitwaghchaure
e02ad91c39 Merge pull request #34771 from frappe/mergify/bp/version-14-hotfix/pr-34768
fix: UX for stock entry, bom and work order (backport #34768)
2023-04-06 14:44:45 +05:30
Rohit Waghchaure
d4a6035c83 fix: UX for stock entry, bom and work order
(cherry picked from commit 82a136f991)
2023-04-06 08:47:08 +00:00
s-aga-r
99226d3811 fix: Subcontracting Receipt incorrect status
(cherry picked from commit a55b818119)
2023-04-06 08:27:32 +00:00
Frappe PR Bot
2a8c9f8e69 chore(release): Bumped to Version 14.20.3
## [14.20.3](https://github.com/frappe/erpnext/compare/v14.20.2...v14.20.3) (2023-04-06)

### Bug Fixes

* Unable to create payment request against purchase invoice ([#34762](https://github.com/frappe/erpnext/issues/34762)) ([f4473b3](f4473b36a5))
2023-04-06 07:54:09 +00:00
mergify[bot]
f4473b36a5 fix: Unable to create payment request against purchase invoice (#34762)
fix: Unable to create payment request against purchase invoice (#34762)

fix: Unable to create payment request against purchase invoice (#34762)

(cherry picked from commit 91a26608ee)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit a1f7e35914)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-04-06 13:22:09 +05:30
mergify[bot]
a1f7e35914 fix: Unable to create payment request against purchase invoice (#34762)
fix: Unable to create payment request against purchase invoice (#34762)

(cherry picked from commit 91a26608ee)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-06 12:49:15 +05:30
Frappe PR Bot
b6ae9a4a72 chore(release): Bumped to Version 14.20.2
## [14.20.2](https://github.com/frappe/erpnext/compare/v14.20.1...v14.20.2) (2023-04-05)

### Bug Fixes

* incorrect stock balance quantity for batch item ([c7cee86](c7cee86685))
2023-04-05 18:57:55 +00:00
rohitwaghchaure
313e1a5e04 Merge pull request #34759 from frappe/mergify/bp/version-14/pr-34758
fix: incorrect stock balance quantity for batch item (backport #34743) (backport #34758)
2023-04-06 00:26:07 +05:30
Rohit Waghchaure
c7cee86685 fix: incorrect stock balance quantity for batch item
(cherry picked from commit ef4bd77196)
(cherry picked from commit d817c50581)
2023-04-05 18:18:47 +00:00
rohitwaghchaure
915c4819b6 Merge pull request #34758 from frappe/mergify/bp/version-14-hotfix/pr-34743
fix: incorrect stock balance quantity for batch item (backport #34743)
2023-04-05 23:47:17 +05:30
Rohit Waghchaure
d817c50581 fix: incorrect stock balance quantity for batch item
(cherry picked from commit ef4bd77196)
2023-04-05 17:52:45 +00:00
Frappe PR Bot
33ee958cfb chore: release v14 (#34733) 2023-04-05 17:41:20 +05:30
mergify[bot]
8ba1e0f31e fix: payment entry is already created on posawesome. (backport #34712) (#34752) 2023-04-05 13:55:13 +05:30
mergify[bot]
3ad5d676ab fix: Shop by category fixes (backport #34688) (#34750)
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:34 +05:30
mergify[bot]
05d24e3665 feat: Auto allocate advance payments only against orders (#34727)
* feat: Auto allocate advance payments only against orders (#34727)

feat: Auto allocate advance payments only againt orders
(cherry picked from commit fd3fb64aa3)

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

* chore: Resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-05 13:00:40 +05:30
mergify[bot]
3c0cc024aa 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:33:55 +05:30
Anand Baburajan
88c8c36805 fix: asset monthly WDV and DD schedule [v14] (#34644)
* fix: monthly wdv and dd schedule

* chore: handle case without pro rata

* chore: fix DD rate and prev depr amount in case of disposal

* chore: minor fix for schedules with just 2 rows

* chore: minor bug

* refactor: get_depreciation_amount

* refactor: another one for get_depreciation_amount
2023-04-05 11:45:45 +05:30
Anand Baburajan
7c4a9b56ff Merge pull request #34737 from AnandBaburajan/asdeprledger_cancelled_deprs
fix: don't include cancelled JVs in assdeprledger report
2023-04-05 11:33:53 +05:30
Anand Baburajan
16e554dd7b Merge branch 'version-14-hotfix' into asdeprledger_cancelled_deprs 2023-04-04 17:51:21 +05:30
anandbaburajan
3896d41e95 fix: don't include cancelled JVs in assdeprledger report 2023-04-04 17:49:16 +05:30
Sagar Sharma
12625d87b0 Merge pull request #34717 from frappe/mergify/bp/version-14-hotfix/pr-34713
fix: consider qty field precision (backport #34713)
2023-04-04 08:45:12 +05:30
Frappe PR Bot
dfadfdc32c chore(release): Bumped to Version 14.20.1
## [14.20.1](https://github.com/frappe/erpnext/compare/v14.20.0...v14.20.1) (2023-04-03)

### Bug Fixes

* bom update log not working for large batch size ([da35436](da354362be))
2023-04-03 17:50:43 +00:00
rohitwaghchaure
e4def081f5 Merge pull request #34724 from frappe/mergify/bp/version-14/pr-34719
fix: bom update log not working for large batch size (backport #34715) (backport #34719)
2023-04-03 23:19:00 +05:30
Sagar Sharma
cbb8dd6aa6 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-34713 2023-04-03 22:23:20 +05:30
Rohit Waghchaure
da354362be fix: bom update log not working for large batch size
(cherry picked from commit d56070301c)
(cherry picked from commit 551190af30)
2023-04-03 16:06:57 +00:00
rohitwaghchaure
9372d46c08 Merge pull request #34711 from vishdha/reserved_pos_qty
fix: use stock qty to calculate POS reserved stock
2023-04-03 21:14:36 +05:30
rohitwaghchaure
dff61ab759 Merge pull request #34719 from frappe/mergify/bp/version-14-hotfix/pr-34715
fix: bom update log not working for large batch size (backport #34715)
2023-04-03 16:55:27 +05:30
Rohit Waghchaure
551190af30 fix: bom update log not working for large batch size
(cherry picked from commit d56070301c)
2023-04-03 10:20:09 +00:00
s-aga-r
2c54e763e4 fix: consider qty field precision
(cherry picked from commit 6ec7590c21)
2023-04-03 10:07:42 +00:00
mergify[bot]
6b866e24f6 fix(ui): recalculate difference amount on allocation change (#34694)
fix: recalculate difference amount on allocation change

(cherry picked from commit 32a4ca6b6c)

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2023-04-03 14:55:32 +05:30
mergify[bot]
be2990ec88 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)

* 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)

* 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

---------

Co-authored-by: marination <maricadsouza221197@gmail.com>
2023-04-03 13:00:22 +05:30
Vishal
c0f7f7da42 fix: use stock qty to calculate POS reserved stock 2023-04-03 12:46:57 +05:30
Sagar Sharma
b34c78c4e7 Merge pull request #34697 from frappe/mergify/bp/version-14-hotfix/pr-34656
fix: BOM Update Cost, when no actual qty (backport #34656)
2023-04-02 19:16:41 +05:30
Sagar Sharma
4ada090cb2 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-34656 2023-04-02 14:59:38 +05:30
mergify[bot]
5677f25215 fix: Multiple issues in purchase invoice submission (#34600)
fix: Multiple issues in purchase invoice submission (#34600)

* fix: Multiple issues in purchase invoice submission

* fix: Base grand total calculation

* chore: Calculate base grand total separately only in multi currency docs

* fix: Add gl entry for round off

(cherry picked from commit 4c61ee30bb)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-02 12:56:58 +05:30
mergify[bot]
f1687cfb14 fix: Bank clearance for case loan (disburstment/repayment) (#34586)
fix: Bank clearance for case loan (disburstment/repayment) (#34586)

(cherry picked from commit 74b29eb5e2)

Co-authored-by: Kitti U. @ Ecosoft <kittiu@ecosoft.co.th>
2023-04-01 22:07:50 +05:30
mergify[bot]
a00459aec3 fix: Supplier RFQ email link (#34338)
fix: Supplier RFQ email link (#34338)

(cherry picked from commit fc86a8568f)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-04-01 22:07:28 +05:30
s-aga-r
8757435898 fix: BOM Update Cost, when no actual qty
(cherry picked from commit a4112c75c5)
2023-04-01 10:54:51 +00:00
ruthra kumar
dcf62dc548 Merge pull request #34686 from frappe/mergify/bp/version-14-hotfix/pr-34679
fix: enclose ternary operator in parenthesis (backport #34679)
2023-03-31 14:05:30 +05:30
ruthra kumar
b835760b0b fix: enclose ternary operator in parentheses
(cherry picked from commit 986daa6578)
2023-03-31 08:02:13 +00:00
Sagar Sharma
bd0c20f789 Merge pull request #34681 from frappe/mergify/bp/version-14-hotfix/pr-34677
chore: make `Production Plan Item Reference` table hidden in Production Plan (backport #34677)
2023-03-31 12:37:19 +05:30
s-aga-r
5ef98fcea1 chore: make Production Plan Item Reference table hidden in Production Plan
(cherry picked from commit 706be2a415)
2023-03-31 07:05:20 +00:00
mergify[bot]
5e28d0234e fix: Total debit and credit while importing via Data Import (#34659)
fix: Total debit and credit while importing via Data Import (#34659)

(cherry picked from commit 7c42b72ee7)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-31 11:58:45 +05:30
mergify[bot]
5e03a4e9e2 fix: Column value mismatch in COA blank template (#34658)
fix: Column value mismatch in COA blank template (#34658)

(cherry picked from commit 576575c227)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-31 11:58:16 +05:30
rohitwaghchaure
e98e64f925 Merge pull request #34669 from frappe/mergify/bp/version-14-hotfix/pr-34664
fix: incorrect balance qty in the stock ledger report (backport #34664)
2023-03-30 18:18:07 +05:30
rohitwaghchaure
9ce281d008 Merge pull request #34671 from frappe/mergify/bp/version-14-hotfix/pr-34636
fix: posting time issue (backport #34636)
2023-03-30 18:17:31 +05:30
Anand Baburajan
27a3f2ce55 Merge pull request #34665 from frappe/mergify/bp/version-14-hotfix/pr-34661
chore: improve asset depr posting failure msg (backport #34661)
2023-03-30 17:50:24 +05:30
mergify[bot]
61858a60c2 fix: plaid log_error syntax issue (backport #34642) (#34667)
fix: plaid log_error syntax issue (#34642)

(cherry picked from commit ddb17a8880)

Co-authored-by: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com>
2023-03-30 17:28:37 +05:30
Rohit Waghchaure
bb5eeb6bd6 fix: posting time issue
(cherry picked from commit 345e6facbe)
2023-03-30 11:56:35 +00:00
Rohit Waghchaure
3494c9ccb6 fix: incorrect balance qty in the stock ledger report
(cherry picked from commit cbdaab940d)
2023-03-30 11:56:27 +00:00
Anand Baburajan
a0df23415b 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:32 +00:00
mergify[bot]
8510c398a4 chore: auto fill asset name and available for use date (backport #34660) (#34662)
* 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:37:35 +05:30
rohitwaghchaure
bae476cc99 Merge pull request #34652 from frappe/mergify/bp/version-14-hotfix/pr-34648
fix: serial no with zero quantity issue in stock reco (backport #34648)
2023-03-30 13:39:58 +05:30
Anand Baburajan
4dfc660cc0 Merge pull request #34650 from frappe/mergify/bp/version-14-hotfix/pr-34649
fix: incorrect arg name in asset value adjustment (backport #34649)
2023-03-30 13:10:26 +05:30
Rohit Waghchaure
f47be46717 fix: serial no with zero quantity issue in stock reco
(cherry picked from commit 17131e5a02)
2023-03-30 07:35:38 +00:00
anandbaburajan
8d9305ee5f fix: incorrect arg name in asset value adjustment
(cherry picked from commit 2b0470d1f5)
2023-03-30 07:34:09 +00:00
Anand Baburajan
6819f0106d Merge pull request #34640 from frappe/mergify/bp/version-14-hotfix/pr-34607
Asset maintenance task add dropdown "3 Yearly" (backport #34607)
2023-03-29 18:13:54 +05:30
Bevan Tony Medrano
e3de229b82 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

(cherry picked from commit 625b8e8005)
2023-03-29 12:15:40 +00:00
mergify[bot]
3e67994cc7 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:00 +05:30
Frappe PR Bot
a60c8f0e18 chore(release): Bumped to Version 14.20.0
# [14.20.0](https://github.com/frappe/erpnext/compare/v14.19.0...v14.20.0) (2023-03-28)

### Bug Fixes

* default pos conversion factor set to 1 ([#34437](https://github.com/frappe/erpnext/issues/34437)) ([18d813a](18d813a656))
* don't get zero value entries for exchange rate calculation ([#34475](https://github.com/frappe/erpnext/issues/34475)) ([ff24b3e](ff24b3e40c))
* incorrect `Opening Value` in `Stock Balance` report ([76b782a](76b782a03f))
* Note username overlapping with note content(CRM) ([096e5ef](096e5ef197))
* Party Name in SOA print when viewed from Customer/Supplier master ([#34597](https://github.com/frappe/erpnext/issues/34597)) ([835edbe](835edbe80e))
* Percentage billing in Sales Order ([#34606](https://github.com/frappe/erpnext/issues/34606)) ([477cb12](477cb12240))
* recalculate WDV rate after asset repair [v14] ([#34571](https://github.com/frappe/erpnext/issues/34571)) ([d2ca6f8](d2ca6f8d1f))
* remove unused translation ([#34519](https://github.com/frappe/erpnext/issues/34519)) ([881e92e](881e92e7b3))
* removing redundant validation ([fd6db41](fd6db41b6e))
* Sales person variance report without item group ([#34552](https://github.com/frappe/erpnext/issues/34552)) ([90ddc4a](90ddc4a1e2))
* Tax Category not able to set hence it calculating zero tax for item whoes tax template set ([#34525](https://github.com/frappe/erpnext/issues/34525)) ([a8567b0](a8567b09e6))
* Time button not working in the job card ([8fed33b](8fed33b03b))
* translations and UX in alternative item mapping ([#34433](https://github.com/frappe/erpnext/issues/34433)) ([702d07e](702d07ea7d))
* unset address and contact on trash (backport [#34495](https://github.com/frappe/erpnext/issues/34495)) ([#34560](https://github.com/frappe/erpnext/issues/34560)) ([db01bf5](db01bf5dec))
* zero rm-cost for batch rm item in SCR (backport [#34616](https://github.com/frappe/erpnext/issues/34616)) ([#34623](https://github.com/frappe/erpnext/issues/34623)) ([cff35d7](cff35d7286))

### Features

* deprecate get_customer_list ([#34563](https://github.com/frappe/erpnext/issues/34563)) ([67576ad](67576ad5bd))
2023-03-28 18:23:52 +00:00
Deepesh Garg
efdbb91a21 Merge pull request #34611 from frappe/version-14-hotfix
chore: release v14
2023-03-28 23:49:38 +05:30
mergify[bot]
cff35d7286 fix: zero rm-cost for batch rm item in SCR (backport #34616) (#34623)
fix: zero rm-cost for batch rm item in SCR (#34616)

fix: `0` rm-cost for batch rm item in SCR
(cherry picked from commit 867d898304)

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2023-03-28 21:09:01 +05:30
rohitwaghchaure
c671f3ddc9 Merge pull request #34621 from frappe/mergify/bp/version-14-hotfix/pr-34461
fix: incorrect `Opening Value` in `Stock Balance` report (backport #34461)
2023-03-28 18:41:42 +05:30
s-aga-r
76b782a03f fix: incorrect Opening Value in Stock Balance report
(cherry picked from commit b04a101c11)
2023-03-28 12:11:54 +00:00
ruthra kumar
6f502bdc54 Merge pull request #34619 from frappe/mergify/bp/version-14-hotfix/pr-34608
chore: removing redundant validation (backport #34608)
2023-03-28 17:21:11 +05:30
mergify[bot]
a8567b09e6 fix: Tax Category not able to set hence it calculating zero tax for item whoes tax template set (#34525)
fix: Tax Category not able to set hence it calculating zero tax for item whoes tax template set (#34525)

* fix: Tax Category not able to set hence it calculating zero tax for item whoes tax template set

* fix: minor change added

(cherry picked from commit 7aafc90d58)

Co-authored-by: Vishal Dhayagude <vishdha@users.noreply.github.com>
2023-03-28 17:20:26 +05:30
ruthra kumar
fd6db41b6e fix: removing redundant validation
(cherry picked from commit d52f7e2820)
2023-03-28 16:59:42 +05:30
mergify[bot]
ff24b3e40c fix: don't get zero value entries for exchange rate calculation (#34475)
fix: don't get zero value entries for exchange rate calculation (#34475)

* fix: multiply None by float

* chore: remove debug

(cherry picked from commit 393bc25e2d)

Co-authored-by: Devin Slauenwhite <devin.slauenwhite@gmail.com>
2023-03-28 16:58:22 +05:30
mergify[bot]
477cb12240 fix: Percentage billing in Sales Order (#34606)
fix: Percentage billing in Sales Order (#34606)

(cherry picked from commit 12ad2aa2e5)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-28 16:52:53 +05:30
mergify[bot]
835edbe80e 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:48:53 +05:30
mergify[bot]
18d813a656 fix: default pos conversion factor set to 1 (#34437)
* fix: default pos conversion factor set to 1 (#34437)

(cherry picked from commit 08fc686513)

# Conflicts:
#	erpnext/selling/page/point_of_sale/point_of_sale.py

* chore: Resolve conflicts

---------

Co-authored-by: Shram Kadia <65490105+Shram007@users.noreply.github.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-24 15:14:52 +05:30
mergify[bot]
67576ad5bd feat: deprecate get_customer_list (#34563)
feat: deprecate get_customer_list (#34563)

(cherry picked from commit 8c7fa5712b)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-03-24 15:14:25 +05:30
ruthra kumar
37f2ba882e Merge pull request #34583 from frappe/mergify/bp/version-14-hotfix/pr-34577
refactor: additional filters and columns in Payment Ledger report (backport #34577)
2023-03-24 14:22:55 +05:30
ruthra kumar
57ecac4aa7 refactor: additional filters and columns in Payment Ledger report (#34577)
1. 'Party type' and 'Party' filters have been added
2. checkbox to include Amount in Acccount Currency
3. Grouping vouchers on Party
4. Replaced Company with Posting Date

(cherry picked from commit f7780cdb58)
2023-03-24 08:11:34 +00:00
rohitwaghchaure
070dea1bc5 Merge pull request #34574 from frappe/mergify/bp/version-14-hotfix/pr-34573
fix: Timer buttons not working in the job card (backport #34573)
2023-03-24 08:40:56 +05:30
Rohit Waghchaure
8fed33b03b fix: Time button not working in the job card
(cherry picked from commit 34c190b7d6)
2023-03-23 17:06:58 +00:00
mergify[bot]
90ddc4a1e2 fix: Sales person variance report without item group (#34552)
fix: Sales person variance report without item group (#34552)

fix: Sales person variance report witout item group
(cherry picked from commit 87108be11a)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-23 21:49:13 +05:30
Anand Baburajan
d2ca6f8d1f fix: recalculate WDV rate after asset repair [v14] (#34571)
fix: recalculate wdv rate after asset repair
2023-03-23 21:08:59 +05:30
mergify[bot]
db01bf5dec fix: unset address and contact on trash (backport #34495) (#34560)
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:32 +05:30
mergify[bot]
881e92e7b3 fix: remove unused translation (#34519)
* fix: remove unused translation (#34519)

(cherry picked from commit 0df3a1a3af)

# Conflicts:
#	erpnext/translations/tr.csv

* chore: resolve conflicts

---------

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-03-23 00:06:14 +05:30
Shariq Ansari
d2e9527563 Merge pull request #34549 from frappe/mergify/bp/version-14-hotfix/pr-34547
fix: Note username overlapping with note content(CRM) (backport #34547)
2023-03-22 12:05:56 +05:30
Shariq Ansari
096e5ef197 fix: Note username overlapping with note content(CRM)
(cherry picked from commit 76cea7dd6a)
2023-03-22 06:32:46 +00:00
mergify[bot]
702d07ea7d fix: translations and UX in alternative item mapping (#34433)
fix: translations and UX in alternative item mapping (#34433)

* fix: disable deletion in alternative item mapping

* feat: german translations

* fix: make string translatable

---------

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

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-03-21 22:48:38 +05:30
Frappe PR Bot
e271935673 chore(release): Bumped to Version 14.19.0
# [14.19.0](https://github.com/frappe/erpnext/compare/v14.18.3...v14.19.0) (2023-03-21)

### Bug Fixes

* **client:** Amount calculation for 0 qty debit notes ([#34455](https://github.com/frappe/erpnext/issues/34455)) ([d24f4d2](d24f4d2873))
* difference amount calculation for company currency accounts ([9ab7bff](9ab7bff0e0))
* don't map item row having `0` qty ([7611a49](7611a49db7))
* E-commerce issue with Item Variants ([53c3fff](53c3fff235))
* german translations ([#34312](https://github.com/frappe/erpnext/issues/34312)) ([dd0c833](dd0c8334cd))
* hide `+` button based on `Blanket Order Type` ([daa1bb8](daa1bb86e3))
* incorrect depr schedules after asset repair [v14] ([#34527](https://github.com/frappe/erpnext/issues/34527)) ([560df63](560df6330a)), closes [#30838](https://github.com/frappe/erpnext/issues/30838)
* Multiple accounting dimension filtering in AR/AP reports ([#34464](https://github.com/frappe/erpnext/issues/34464)) ([f146479](f146479362))
* Overallocation of 'qty' from Cr Notes to Parent Invoice ([848e56b](848e56bd4c))
* patch depends on Currency Exchange Settings ([#34494](https://github.com/frappe/erpnext/issues/34494)) ([4acde44](4acde4468f))
* POS not picking up pos profile company address instead fetch any random company address ([#34521](https://github.com/frappe/erpnext/issues/34521)) ([01f4cc7](01f4cc76fc))
* Update account number from parent company ([#34474](https://github.com/frappe/erpnext/issues/34474)) ([55d002c](55d002c636))
* use max function to get default company address (backport [#34116](https://github.com/frappe/erpnext/issues/34116)) ([#34452](https://github.com/frappe/erpnext/issues/34452)) ([ba2fd71](ba2fd71b65))

### Features

* add field `Over Order Allowance (%)` in `Buying Settings` ([da915f1](da915f1510))
* add field `Over Order Allowance (%)` in `Selling Settings` ([46b5ba9](46b5ba9c2a))
* bank reconciliation and plaid changes ([#33986](https://github.com/frappe/erpnext/issues/33986)) ([9b608ea](9b608eaa0f))
* consider `over_order_allowance` while validating order qty ([932639b](932639b4df))
* consider `over_order_allowance` while validating sales order qty ([09b577a](09b577a91f))
* Support for Alternative Items in Quotation ([#33874](https://github.com/frappe/erpnext/issues/33874)) ([9f7da21](9f7da21c93))

### Performance Improvements

* index against_sales_invoice field on DN items (backport [#34509](https://github.com/frappe/erpnext/issues/34509)) ([#34510](https://github.com/frappe/erpnext/issues/34510)) ([baa789b](baa789be34))
2023-03-21 12:52:05 +00:00
Deepesh Garg
78f4082893 Merge pull request #34532 from frappe/version-14-hotfix
chore: release v14
2023-03-21 18:20:39 +05:30
mergify[bot]
dd0c8334cd 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:32 +05:30
mergify[bot]
01f4cc76fc fix: POS not picking up pos profile company address instead fetch any random company address (#34521)
fix: POS not picking up pos profile company address instead fetch any random company address (#34521)

(cherry picked from commit 6966fa4d88)

Co-authored-by: Vishal Dhayagude <vishdha@users.noreply.github.com>
2023-03-21 16:06:54 +05:30
mergify[bot]
d24f4d2873 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:05:41 +05:30
mergify[bot]
17744a99a1 refactor(bank reconciliation tool): currency symbol fix and concurrent usage (#34501)
* fix: incorrect currency symbol in Bank Reconciliation tool

(cherry picked from commit 2d14d92b32)

* refactor: allow for concurrent use of reconciliation tool

1. set default filter dates a period of one month from current date

(cherry picked from commit 1eea585d29)

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2023-03-21 16:01:09 +05:30
rohitwaghchaure
8c40dd93c3 Merge pull request #34530 from frappe/mergify/bp/version-14-hotfix/pr-34528
fix: E-commerce issue with Item Variants (backport #34528)
2023-03-21 14:52:39 +05:30
Rohit Waghchaure
53c3fff235 fix: E-commerce issue with Item Variants
(cherry picked from commit aaa4d1eb55)
2023-03-21 09:17:33 +00:00
Anand Baburajan
560df6330a fix: incorrect depr schedules after asset repair [v14] (#34527)
* fix: backport missing changes from #30838

* fix: incorrect schedule after repair
2023-03-21 14:31:23 +05:30
ruthra kumar
91a609d2ab Merge pull request #34513 from ruthra-kumar/manual_backport_of_34456_to_v14
fix: Gross Profit reports Invoices with -ve qty for Invoices with Cr Notes (manual backport to version 14)
2023-03-20 17:05:28 +05:30
ruthra kumar
aead554d31 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
2023-03-20 16:06:53 +05:30
ruthra kumar
e0e89b4209 refactor: Ignore linked Cr Notes in Report output 2023-03-20 16:06:53 +05:30
ruthra kumar
848e56bd4c 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.
2023-03-20 16:06:53 +05:30
mergify[bot]
baa789be34 perf: index against_sales_invoice field on DN items (backport #34509) (#34510)
perf: index against_sales_invoice field on DN items (#34509)

This is used on Sales invoice dashboard and takes a lot of time to load
as db size increases.

Results:

Before: ~10-20 seconds to load dashboard
After: few milliseconds because of index

[skip ci]

(cherry picked from commit 109a9f1390)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-03-20 14:48:46 +05:30
mergify[bot]
4acde4468f fix: patch depends on Currency Exchange Settings (#34494)
fix: patch depends on Currency Exchange Settings (#34494)

(cherry picked from commit d791dc11a3)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-03-20 14:27:34 +05:30
mergify[bot]
de5fabc67a chore: Update user manual link (#34478)
* chore: Update user manual link (#34478)

(cherry picked from commit be723bb9d4)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-19 18:06:45 +05:30
mergify[bot]
f146479362 fix: Multiple accounting dimension filtering in AR/AP reports (#34464)
fix: Multiple accounting dimension filtering in AR/AP reports (#34464)

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

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-19 18:06:00 +05:30
mergify[bot]
55d002c636 fix: Update account number from parent company (#34474)
fix: Update account number from parent company (#34474)

(cherry picked from commit d8ece86463)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-19 18:05:33 +05:30
Sagar Sharma
b20296b2fe Merge pull request #34487 from frappe/mergify/bp/version-14-hotfix/pr-34440
chore: `Allow Zero Valuation Rate` msg in SE (backport #34440)
2023-03-17 16:56:37 +05:30
s-aga-r
5604074935 chore: Allow Zero Valuation Rate msg in SE
(cherry picked from commit 22ad9a1903)
2023-03-17 10:33:42 +00:00
ruthra kumar
bd1b955eb6 Merge pull request #34481 from frappe/mergify/bp/version-14-hotfix/pr-34466
fix: unwanted difference amt while reconciling vouchers from base currency account (backport #34466)
2023-03-17 15:26:10 +05:30
ruthra kumar
c71b4ed6ec refactor: difference amt validation for same currency accounts
(cherry picked from commit ec075122b6)
2023-03-17 09:22:32 +00:00
ruthra kumar
e81ad864cf test: difference amount should not be calculated for base currency
(cherry picked from commit 861387f164)
2023-03-17 09:22:32 +00:00
ruthra kumar
9ab7bff0e0 fix: difference amount calculation for company currency accounts
(cherry picked from commit 48fae0c1ce)
2023-03-17 09:22:32 +00:00
Sagar Sharma
524ed324fb Merge pull request #34480 from frappe/mergify/bp/version-14-hotfix/pr-34279
fix: `Blanket Order` (backport #34279)
2023-03-17 11:03:33 +05:30
s-aga-r
c46e5a81d4 test: add test cases for Over Order Allowance against Blanket Order
(cherry picked from commit 66f650061d)
2023-03-17 04:45:27 +00:00
s-aga-r
09b577a91f feat: consider over_order_allowance while validating sales order qty
(cherry picked from commit 53701c37b1)
2023-03-17 04:45:27 +00:00
s-aga-r
46b5ba9c2a feat: add field Over Order Allowance (%) in Selling Settings
(cherry picked from commit d7da8928ac)
2023-03-17 04:45:26 +00:00
s-aga-r
932639b4df feat: consider over_order_allowance while validating order qty
(cherry picked from commit 8bcbc45add)
2023-03-17 04:45:26 +00:00
s-aga-r
7611a49db7 fix: don't map item row having 0 qty
(cherry picked from commit fc1088d9c4)
2023-03-17 04:45:26 +00:00
s-aga-r
35297f6ac1 refactor: rewrite blanket_order.py queries in QB
(cherry picked from commit f3993783a3)
2023-03-17 04:45:26 +00:00
s-aga-r
da915f1510 feat: add field Over Order Allowance (%) in Buying Settings
(cherry picked from commit f5937f46cb)
2023-03-17 04:45:26 +00:00
s-aga-r
daa1bb86e3 fix: hide + button based on Blanket Order Type
(cherry picked from commit abf9a28d6a)
2023-03-17 04:45:25 +00:00
Raffael Meyer
9b608eaa0f feat: bank reconciliation and plaid changes (#33986)
feat: bank reconciliation and plaid changes (#33986)

fix: plaid link refresh: update account ids
fix: plaid transactions for credit cards & add accounts on link refresh if they don't exist
fix: bank reconciliation amount matching
fix: bank reconciliation dialog usability
feat: rewrite bank transaction reconciliation to allow multiple transactions to reconcile against vouchers before clearance
fix: matching transaction amounts and race condition bug
fix: ensure there is a reference number in plaid transactions and other tweaks
feat: add references to Payroll Entry Bank Journal Entry
feat: only clear Voucher once all Bank GLEs are allocated to Bank Transactions
fix: strange type error
feat: add payment method field to bank and plaid transactions and prepopulate relevant bank reconciliation new voucher fields
feat: bank reconciliation - allow bank transactions to reconcile against themselves for when there are banking amendments
fix: bank transaction self-reconcile bug and tidy
fix: bank reconciliation datatable index update

Co-authored-by: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com>
2023-03-17 09:01:29 +05:30
Ritwik Puri
befd1a0f91 ci: use version specific payments repo (#34468)
ci: use version-14 branch of payments repo for v14 erpnext
2023-03-16 16:04:21 +05:30
mergify[bot]
9f7da21c93 feat: Support for Alternative Items in Quotation (#33874)
* feat: Filter out alternative item rows in taxes and totals for Quotation

- Added a Quotation Item field `is_alternative_item`
- Use filtered rows for taxes and totals computation

(cherry picked from commit 91982d1e4f)

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

* feat: Consider filtered items table in JS for totals computation

- Set `_items` as filtered rows if quotation else the entire table. Set at entry point of JS API
- Use `_items` instead of `items` to compute taxes and charges. Exclude alternative item rows

(cherry picked from commit f19eadab9a)

* feat: Dialog to select alternative item before creating Sales order

- Users can leave the row blank in the dialog if original item is to be used
- Else users can select an alternative item against an original item
- In the document, users must check `Is Alternative Item` if needed and also specify which item it is an altenrative to since there are no documented mappings

(cherry picked from commit cef7dfd0b4)

# Conflicts:
#	erpnext/selling/doctype/quotation/quotation.js
#	erpnext/selling/doctype/quotation_item/quotation_item.json

* feat: Filter rows to be mapped on server side mapping function

- Pass dialog selections to `make_sales_order`
- Map either original item or its alternative depending on mapping
- Only qty check for simple rows (without alternatives and not an alternative itself)

(cherry picked from commit 94cacb60de)

* chore: Validate 'alternative_to' field values, must be a valid non-alterntaive item from table

(cherry picked from commit fa9b327501)

* fix: Iterate over list instead of map's output and formatting

(cherry picked from commit ece6358e60)

* fix: Consider only ordered alternative/original item for Quotation status

- The original and its alternatives make a set of items where one is chosen
- While setting order status of Quotation, check if the chosen item from the set is fully ordered or not
- Filter out unselected items from the set
- Create a map containing the set of items and if they were ordered or not for ease of grouping
- The simple items will work as it used to

(cherry picked from commit b3fe7c6dad)

* chore: Code simplification

- Map is not required, avoid filter multiple times, use single loop instead
- Better variable name
- Reduce LOC

(cherry picked from commit 03321f5f13)

* refactor: Order based alternative items mapping

- Alternatives must be followed by a non-alternative item row
- On submit, store non-alternative rows in hidden checkbox to avoid recomputation
- Check for valid/mappable rows by row name
- UI: Select from table rows.Add single row for original/alternative item in dialog
- UI: Indicator for alternative items in dialog grid
- UI: Indicator legend and description of table
- DB: Added check field 'Has Alternative Item' not to be confused with 'Has Alternative' in Mfg

(cherry picked from commit db2076db69)

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

* test: Alternative items in Quotation

- Taxes and totals, mapping, back updation

(cherry picked from commit 74fab53e28)

* fix: Use block variable

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 3c96791d52)

* fix: Handle `Get Items From` in Sales Order

- Map all non alternatives from Quotation to SO if no selected items
- Show disclaimer mentioning that Qtns with alternatives must be mapped to SO from the Qtn form

(cherry picked from commit 19456127cf)

* fix: Map only non alternative items from Quotation in Sales Invoice

- Since there's no item selection, only Quotation selection :/

(cherry picked from commit 6b789e2f04)

* fix: Merge conflicts

---------

Co-authored-by: marination <maricadsouza221197@gmail.com>
2023-03-16 11:50:19 +05:30
mergify[bot]
68f9863ae5 test: add timeout to all BOM related tests (backport #34446) (#34453)
test: add timeout to all BOM related tests (#34446)

* Revert "chore: remove failing test (#34444)"

This reverts commit b89ecd482d.

* test: add timeout to bom tests

(cherry picked from commit f95ad039e4)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-03-15 14:19:09 +05:30
mergify[bot]
ba2fd71b65 fix: use max function to get default company address (backport #34116) (#34452)
* fix: use max function to get default company address

(cherry picked from commit b93c18bd4a)

* test: add test for primary address sorting

(cherry picked from commit e0042972c8)

---------

Co-authored-by: Prateek <40106895+prateekkaramchandani@users.noreply.github.com>
Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-03-15 10:38:49 +05:30
Frappe PR Bot
3e61e76317 chore(release): Bumped to Version 14.18.3
## [14.18.3](https://github.com/frappe/erpnext/compare/v14.18.2...v14.18.3) (2023-03-14)

### Bug Fixes

* `BOM Stock Report` ([1c00077](1c0007768b))
* `required_qty` get reset to `1` for Alternative Item in WO ([51bcdb3](51bcdb32f2))
* Don't use get_list & get_all interchangeably ([27c524e](27c524e337))
* Error in consolidated financial statement ([#34330](https://github.com/frappe/erpnext/issues/34330)) ([73866f4](73866f4da7))
* exchange rate revaluation errors ([#33947](https://github.com/frappe/erpnext/issues/33947)) ([1a629b6](1a629b6418))
* filters not getting applied on `Web Form` ([6ef7ddf](6ef7ddfbce))
* Linked invoice cancellation issue via timesheet ([#34337](https://github.com/frappe/erpnext/issues/34337)) ([da8cc2b](da8cc2bba9))
* operation time for multi-level BOM in WO ([76e04c8](76e04c8625))
* Set contact filter link in Opportunity ([#34325](https://github.com/frappe/erpnext/issues/34325)) ([c64836d](c64836d3d6))
* set tax category from address before executing `get_regional_address_details` ([#34372](https://github.com/frappe/erpnext/issues/34372)) ([bf0cbe0](bf0cbe09b9))
* **test:** flaky test case in Payment terms report ([69a5411](69a5411f0e))
* Total row in trail balance report ([#34395](https://github.com/frappe/erpnext/issues/34395)) ([c353ba7](c353ba741c))
* Use customer name instead of name(id) in PSOA (backport [#34412](https://github.com/frappe/erpnext/issues/34412)) ([#34425](https://github.com/frappe/erpnext/issues/34425)) ([209adf3](209adf32a5))

### Performance Improvements

* `update_completed_qty()` in `material_request.py` ([b37712c](b37712c038))
* Stock Entry (Material Transfer) ([1b51463](1b514632d2))

### Reverts

* Revert "Update tr.csv (backport #34285)" (#34427) ([b6d059c](b6d059ccb8)), closes [#34285](https://github.com/frappe/erpnext/issues/34285) [#34427](https://github.com/frappe/erpnext/issues/34427) [#34285](https://github.com/frappe/erpnext/issues/34285)
* Revert "fix: Default sales team not getting set" (#34376) ([ed338b1](ed338b1395)), 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 17:37:03 +00:00
Deepesh Garg
c44579ff52 Merge pull request #34442 from frappe/version-14-hotfix
chore: release v14
2023-03-14 23:05:05 +05:30
Sagar Sharma
3f7e82f8b1 Merge pull request #34448 from frappe/mergify/bp/version-14-hotfix/pr-34415
fix: operation time for multi-level BOM in WO (backport #34415)
2023-03-14 19:55:38 +05:30
Sagar Sharma
6787a1fe91 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-34415 2023-03-14 18:55:51 +05:30
Sagar Sharma
12fa080c00 Merge pull request #34450 from frappe/mergify/bp/version-14-hotfix/pr-34381
chore: fix french translation (backport #34381)
2023-03-14 18:55:07 +05:30
HENRY Florian
6303d2d8e1 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
76e04c8625 fix: operation time for multi-level BOM in WO
(cherry picked from commit 442ee3adba)
2023-03-14 13:18:30 +00:00
mergify[bot]
c353ba741c fix: Total row in trail balance report (#34395)
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:08:33 +05:30
mergify[bot]
bf0cbe09b9 fix: set tax category from address before executing get_regional_address_details (#34372)
fix: set tax category from address before executing `get_regional_address_details` (#34372)

(cherry picked from commit 5c06620f97)

Co-authored-by: Sagar Vora <sagar@resilient.tech>
2023-03-13 21:15:43 +05:30
mergify[bot]
630386fd8c chore: Move source and campaign to additional info section (#34414)
* chore: Move source and campaign to additional info section (#34414)

(cherry picked from commit c8cc3fc65f)

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

* chore: resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-13 21:11:49 +05:30
mergify[bot]
209adf32a5 fix: Use customer name instead of name(id) in PSOA (backport #34412) (#34425)
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 21:11:22 +05:30
Deepesh Garg
b6d059ccb8 Revert "Update tr.csv (backport #34285)" (#34427)
Revert "Update tr.csv (#34285)"

This reverts commit 5266a7e8a7.
2023-03-13 19:43:26 +05:30
mergify[bot]
da8cc2bba9 fix: Linked invoice cancellation issue via timesheet (#34337)
fix: Linked invoice cancellation issue via timesheet (#34337)

(cherry picked from commit 4416ddc4af)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-13 18:58:54 +05:30
mergify[bot]
af629f92f0 test: fix hypothesis tests (backport #34416) (#34418)
test: fix hypothesis tests (#34416)

(cherry picked from commit b8a61be080)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-03-13 15:28:30 +05:30
mergify[bot]
5266a7e8a7 Update tr.csv (#34285)
chore: Improve Turkish language translation

chore: Improve Turkish language translation
(cherry picked from commit fa6d37542b)

Co-authored-by: Mehmet Demirel <unibravo@gmail.com>
2023-03-13 14:03:38 +05:30
mergify[bot]
73866f4da7 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:03:02 +05:30
ruthra kumar
b0c5f5594d Merge pull request #34413 from frappe/mergify/bp/version-14-hotfix/pr-34408
chore: delete remarks migration patch (backport #34408)
2023-03-13 10:24:42 +05:30
ruthra kumar
502a45e54f chore: delete remarks migration patch
Versions higher than V14.18.2, 'remarks' will be moved in 'migrate_gl_to_payment_ledger'

(cherry picked from commit da37573b73)
2023-03-13 03:45:17 +00:00
ruthra kumar
f854316eb2 Merge pull request #34405 from frappe/mergify/bp/version-14-hotfix/pr-34387
refactor(patch): remove inner join to improve SQL performance (backport #34387)
2023-03-12 13:16:01 +05:30
ruthra kumar
0b184667fc chore: remove remarks migrations patch from patches.txt
'Remarks' field is moved in migrate_gl_to_payment_ledger patch itself
from versions highers than v14.18.2. Removing it from patches.txt

(cherry picked from commit 9d0a1149d8)
2023-03-12 11:08:34 +05:30
ruthra kumar
3923044d88 Merge pull request #34400 from frappe/mergify/bp/version-14-hotfix/pr-34370
fix(test): flaky test case in Payment terms report (backport #34370)
2023-03-11 21:29:56 +05:30
ruthra kumar
0ef1d1b2ae refactor: add remarks to column as well
(cherry picked from commit 1744f1d4e4)
2023-03-11 15:31:17 +00:00
ruthra kumar
e6de87a1b7 refactor(patch): remove inner join to improve SQL performance
(cherry picked from commit f9cfabf78e)
2023-03-11 15:31:16 +00:00
mergify[bot]
c64836d3d6 fix: Set contact filter link in Opportunity (#34325)
fix: Set contact filter link in Opportunity (#34325)

Co-authored-by: Nihantra C. Patel <n.patel.serpentcs@gmail.com>
(cherry picked from commit 71de72bdd0)

Co-authored-by: Solufyin <34390782+Solufyin@users.noreply.github.com>
2023-03-11 19:05:26 +05:30
ruthra kumar
69a5411f0e fix(test): flaky test case in Payment terms report
(cherry picked from commit 7fcd74ed03)
2023-03-11 08:49:03 +00:00
Sagar Sharma
29c58b6f75 Merge pull request #34384 from frappe/mergify/bp/version-14-hotfix/pr-34383
fix: filters not getting applied on `Web Form` (backport #34383)
2023-03-10 13:48:38 +05:30
s-aga-r
6ef7ddfbce fix: filters not getting applied on Web Form
(cherry picked from commit 9c1e566394)
2023-03-09 18:55:03 +00:00
gavin
27c524e337 fix: Don't use get_list & get_all interchangeably
fix: Fetch all fields via get_returned_qty_map_for_row
2023-03-09 16:05:37 +05:30
Frappe PR Bot
fcbcbc0aa7 chore(release): Bumped to Version 14.18.2
## [14.18.2](https://github.com/frappe/erpnext/compare/v14.18.1...v14.18.2) (2023-03-09)

### Reverts

* Revert "fix: Default sales team not getting set" (#34376) ([f71d85d](f71d85d7c3)), 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:37 +00:00
Deepesh Garg
7ca3130010 Merge pull request #34379 from frappe/mergify/bp/version-14/pr-34377
Revert "fix: Default sales team not getting set" (backport #34376) (backport #34377)
2023-03-09 15:43:17 +05:30
mergify[bot]
f71d85d7c3 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 ed338b1395)
2023-03-09 10:11:18 +00:00
mergify[bot]
ed338b1395 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:39:56 +05:30
Sagar Sharma
9f0dff9e7a Merge pull request #34368 from frappe/mergify/bp/version-14-hotfix/pr-34360
chore: `Alternative Item Code` error msg (backport #34360)
2023-03-09 11:10:13 +05:30
Sagar Sharma
31f9d23b17 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-34360 2023-03-09 11:09:48 +05:30
Sagar Sharma
ce8e3e92dd Merge pull request #34366 from frappe/mergify/bp/version-14-hotfix/pr-34362
fix: `required_qty` get reset to `1` for Alternative Item in WO (backport #34362)
2023-03-09 11:09:25 +05:30
s-aga-r
c734a78f3c chore: Alternative Item Code error msg
(cherry picked from commit baef5ae1ef)
2023-03-09 04:38:21 +00:00
s-aga-r
51bcdb32f2 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
Sagar Sharma
68b9581176 Merge pull request #34356 from frappe/mergify/bp/version-14-hotfix/pr-34352
fix: `BOM Stock Report` (backport #34352)
2023-03-08 17:45:52 +05:30
s-aga-r
df98e25312 test: add test cases for BOM Stock Report
(cherry picked from commit b53dcb04ed)
2023-03-08 10:56:44 +00:00
s-aga-r
1c0007768b fix: BOM Stock Report
(cherry picked from commit a65b80911b)
2023-03-08 10:56:44 +00:00
Deepesh Garg
d42af42cec Merge pull request #34331 from frappe/mergify/bp/version-14-hotfix/pr-33947
fix: exchange rate revaluation errors (backport #33947)
2023-03-08 13:07:04 +05:30
Frappe PR Bot
cfe28663bc chore(release): Bumped to Version 14.18.1
## [14.18.1](https://github.com/frappe/erpnext/compare/v14.18.0...v14.18.1) (2023-03-07)

### Performance Improvements

* `update_completed_qty()` in `material_request.py` ([7a5f7d4](7a5f7d4920))
* Stock Entry (Material Transfer) ([59a415e](59a415eaa9))
2023-03-07 17:23:00 +00:00
Sagar Sharma
943599f3ac Merge pull request #34342 from frappe/mergify/bp/version-14/pr-34335
perf: Stock Entry (Material Transfer) (backport #34313) (backport #34335)
2023-03-07 22:51:22 +05:30
s-aga-r
7a5f7d4920 perf: update_completed_qty() in material_request.py
(cherry picked from commit 8ad9e99cea)
(cherry picked from commit b37712c038)
2023-03-07 16:14:55 +00:00
s-aga-r
59a415eaa9 perf: Stock Entry (Material Transfer)
(cherry picked from commit de18f98c5c)
(cherry picked from commit 1b514632d2)
2023-03-07 16:14:55 +00:00
Sagar Sharma
a18c4c839e Merge pull request #34335 from frappe/mergify/bp/version-14-hotfix/pr-34313
perf: Stock Entry (Material Transfer) (backport #34313)
2023-03-07 21:43:05 +05:30
Frappe PR Bot
0696128acc chore(release): Bumped to Version 14.18.0
# [14.18.0](https://github.com/frappe/erpnext/compare/v14.17.4...v14.18.0) (2023-03-07)

### Bug Fixes

* `Inventory Dimension` for `Stock Reconciliation` ([b08cdc0](b08cdc00f2))
* `rejected_serial_no` not getting copied from PR to PR(Return) ([3db8258](3db82587eb))
* `Serial No is mandatory` even if the `qty` is `0` ([aa6b891](aa6b891ef0))
* BOM Update log not completed ([235ecca](235ecca9fa))
* consumed qty validation for subcontracting receipt ([7eccf43](7eccf431fd))
* Default sales team not getting set ([#34284](https://github.com/frappe/erpnext/issues/34284)) ([64c758d](64c758d0c0))
* Do not calculate commission post submit ([#34267](https://github.com/frappe/erpnext/issues/34267)) ([480797e](480797e856))
* labels name ([5e9f1df](5e9f1dfbb3))
* **minor:** Dirty the form after clicking on Get advances button in Invoices ([#34323](https://github.com/frappe/erpnext/issues/34323)) ([0e9f9c3](0e9f9c31a0))
* Payment Request against sales order with disabled rounded total ([#34281](https://github.com/frappe/erpnext/issues/34281)) ([ca59c69](ca59c699cd))
* Performance improvement when adding a new item ([#34195](https://github.com/frappe/erpnext/issues/34195)) ([71a281f](71a281fb11))
* Resolve conflicts ([f6469d8](f6469d8398))
* Stock Reconciliation `actual_qty` ([d97c1bf](d97c1bf0f4))
* update inventory dimensions before returning sle ([ab73742](ab737424c2))
* Wrap unexpectedly long text in remark ([b13bf1e](b13bf1ebc5))

### Features

* adjust purchase receipt valuation rate as per purchase invoice rate ([db033c6](db033c6862))

### Reverts

* Revert "refactor: use renamed timezone utils (#34301)" ([a2e001a](a2e001a2da)), closes [#34301](https://github.com/frappe/erpnext/issues/34301) [#34301](https://github.com/frappe/erpnext/issues/34301)
2023-03-07 14:26:16 +00:00
Deepesh Garg
1cf79f05c5 Merge pull request #34327 from frappe/version-14-hotfix
chore: release v14
2023-03-07 19:54:30 +05:30
s-aga-r
b37712c038 perf: update_completed_qty() in material_request.py
(cherry picked from commit 8ad9e99cea)
2023-03-07 12:33:48 +00:00
s-aga-r
1b514632d2 perf: Stock Entry (Material Transfer)
(cherry picked from commit de18f98c5c)
2023-03-07 12:33:47 +00:00
Deepesh Garg
a2e001a2da Revert "refactor: use renamed timezone utils (#34301)"
Revert "refactor: use renamed timezone utils (#34301)"

This reverts commit 164933aae8.
2023-03-07 17:56:57 +05:30
Devin Slauenwhite
1a629b6418 fix: exchange rate revaluation errors (#33947)
* fix: set new balance for non-positive balances

* fix: don't add debit: 0, credit: 0 entries to journal entry.

* fix: add journal entry difference to unbooked gain/loss of exchange.

* chore: linter

* chore: remove invlaid TODO. [skip-ci]

(cherry picked from commit 6de826b8c4)
2023-03-07 11:46:58 +00:00
mergify[bot]
164933aae8 refactor: use renamed timezone utils (#34301)
refactor: use renamed timezone utils

https://github.com/frappe/frappe/pull/20253
(cherry picked from commit 502a37a864)

Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-07 16:52:56 +05:30
mergify[bot]
0e9f9c31a0 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:54 +05:30
mergify[bot]
71a281fb11 fix: Performance improvement when adding a new item (#34195)
fix: Performance improvement when adding a new item

(cherry picked from commit 49af5ba434)

Co-authored-by: HarryPaulo <paulo_fabris@hotmail.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-03-07 15:46:18 +05:30
mergify[bot]
64c758d0c0 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 15:45:43 +05:30
mergify[bot]
480797e856 fix: Do not calculate commission post submit (#34267)
fix: Do not calculate commission post submit (#34267)

* fix: Do not calculate commision post submit

* chore: Update condition to match server side logic

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 10632d75b0)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-07 15:44:15 +05:30
mergify[bot]
ca59c699cd fix: Payment Request against sales order with disabled rounded total (#34281)
fix: Payment Request against sales order with disabled rounded total (#34281)

* fix: Payment Request against sales order with disabled rounded total

* chore: Do not consider advance amount

(cherry picked from commit ea8e23384d)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-03-07 13:34:25 +05:30
mergify[bot]
9b84e1e39c 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)

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:45:51 +05:30
rohitwaghchaure
cfb93b6c58 Merge pull request #34316 from frappe/mergify/bp/version-14-hotfix/pr-34305
fix: BOM Update log not completed (backport #34305)
2023-03-07 08:39:09 +05:30
Rohit Waghchaure
235ecca9fa fix: BOM Update log not completed
(cherry picked from commit 2f157fa5d3)
2023-03-06 17:54:50 +00:00
Sagar Sharma
c49be03d0f Merge pull request #34300 from frappe/mergify/bp/version-14-hotfix/pr-34299
fix: Stock Reconciliation `actual_qty` (backport #34299)
2023-03-04 18:00:33 +05:30
s-aga-r
d97c1bf0f4 fix: Stock Reconciliation actual_qty
(cherry picked from commit 70de444b7b)
2023-03-04 12:05:49 +00:00
Sagar Sharma
2f74427132 Merge pull request #34294 from frappe/mergify/bp/version-14-hotfix/pr-34293
fix: `Inventory Dimension` for `Stock Reconciliation` (backport #34293)
2023-03-04 16:49:59 +05:30
s-aga-r
ab737424c2 fix: update inventory dimensions before returning sle 2023-03-04 16:07:59 +05:30
s-aga-r
b08cdc00f2 fix: Inventory Dimension for Stock Reconciliation
(cherry picked from commit 0e1b7760a8)
2023-03-03 20:43:56 +00:00
Deepesh Garg
829bbdd5c5 Merge pull request #34280 from frappe/mergify/bp/version-14-hotfix/pr-34258
chore: Make finance book read only (backport #34258)
2023-03-03 11:09:13 +05:30
Deepesh Garg
1cdf7e0988 chore: Make finance book read only
(cherry picked from commit 28dd1a25cb)
2023-03-02 11:18:42 +00:00
Sagar Sharma
fa1b25d0f2 Merge pull request #34278 from frappe/mergify/bp/version-14-hotfix/pr-34117
refactor: rewrite `get_item_details.py` queries in `QB` (backport #34117)
2023-03-02 15:25:12 +05:30
s-aga-r
731dc4cdd9 chore: Linters
(cherry picked from commit 58c027d4cc)
2023-03-02 09:25:38 +00:00
s-aga-r
dea5290d81 refactor: remove method get_serial_no_batchwise from get_item_details.py
(cherry picked from commit 35489fbbf9)
2023-03-02 09:25:38 +00:00
s-aga-r
1e086db7c7 refactor: rewrite get_item_details.py queries in QB
(cherry picked from commit 6b144baa69)
2023-03-02 09:25:38 +00:00
Frappe PR Bot
a59c580480 chore(release): Bumped to Version 14.17.4
## [14.17.4](https://github.com/frappe/erpnext/compare/v14.17.3...v14.17.4) (2023-03-02)

### Bug Fixes

* `rejected_serial_no` not getting copied from PR to PR(Return) ([9930adc](9930adcd28))
* `Serial No is mandatory` even if the `qty` is `0` ([7629caa](7629caa647))
2023-03-02 08:08:44 +00:00
Sagar Sharma
ba1cfa992d Merge pull request #34277 from frappe/mergify/bp/version-14/pr-34275
fix: `rejected_serial_no` not getting copied from PR to PR(Return) (backport #34273) (backport #34275)
2023-03-02 13:34:32 +05:30
s-aga-r
7629caa647 fix: Serial No is mandatory even if the qty is 0
(cherry picked from commit cb0b6de4b9)
(cherry picked from commit aa6b891ef0)
2023-03-02 07:35:46 +00:00
s-aga-r
9930adcd28 fix: rejected_serial_no not getting copied from PR to PR(Return)
(cherry picked from commit a9f0a11ce6)
(cherry picked from commit 3db82587eb)
2023-03-02 07:35:45 +00:00
Sagar Sharma
ab8ea2371b Merge pull request #34275 from frappe/mergify/bp/version-14-hotfix/pr-34273
fix: `rejected_serial_no` not getting copied from PR to PR(Return) (backport #34273)
2023-03-02 13:04:51 +05:30
s-aga-r
aa6b891ef0 fix: Serial No is mandatory even if the qty is 0
(cherry picked from commit cb0b6de4b9)
2023-03-02 07:08:15 +00:00
s-aga-r
3db82587eb fix: rejected_serial_no not getting copied from PR to PR(Return)
(cherry picked from commit a9f0a11ce6)
2023-03-02 07:08:14 +00:00
Suraj Shetty
da150e1a3c Merge pull request #34265 from frappe/mergify/bp/version-14-hotfix/pr-34262
fix(General Ledger): Wrap unexpectedly long word  (backport #34262)
2023-03-01 16:27:57 +05:30
Suraj Shetty
f6469d8398 fix: Resolve conflicts 2023-03-01 16:25:25 +05:30
Suraj Shetty
b13bf1ebc5 fix: Wrap unexpectedly long text in remark
(cherry picked from commit ba66a6714c)

# Conflicts:
#	erpnext/accounts/report/general_ledger/general_ledger.html
2023-03-01 10:53:38 +00:00
Frappe PR Bot
60a1e10b11 chore(release): Bumped to Version 14.17.3
## [14.17.3](https://github.com/frappe/erpnext/compare/v14.17.2...v14.17.3) (2023-03-01)

### Bug Fixes

* consumed qty validation for subcontracting receipt ([6ba9750](6ba97504ed))
2023-03-01 10:22:45 +00:00
rohitwaghchaure
1b578483f4 Merge pull request #34261 from frappe/mergify/bp/version-14/pr-34260
fix: consumed qty validation for subcontracting receipt (backport #34254) (backport #34260)
2023-03-01 15:41:54 +05:30
Rohit Waghchaure
6ba97504ed fix: consumed qty validation for subcontracting receipt
(cherry picked from commit b38fe24090)
(cherry picked from commit 7eccf431fd)
2023-03-01 10:10:02 +00:00
rohitwaghchaure
5e51ba2342 Merge pull request #34260 from frappe/mergify/bp/version-14-hotfix/pr-34254
fix: consumed qty validation for subcontracting receipt (backport #34254)
2023-03-01 15:39:29 +05:30
Rohit Waghchaure
7eccf431fd fix: consumed qty validation for subcontracting receipt
(cherry picked from commit b38fe24090)
2023-03-01 09:46:22 +00:00
rohitwaghchaure
0d6a2aed3e Merge pull request #34242 from frappe/mergify/bp/version-14-hotfix/pr-34235
feat: adjust purchase receipt valuation rate as per purchase invoice rate (backport #34235)
2023-02-28 22:11:21 +05:30
Frappe PR Bot
cc4448b5d5 chore(release): Bumped to Version 14.17.2
## [14.17.2](https://github.com/frappe/erpnext/compare/v14.17.1...v14.17.2) (2023-02-28)

### Bug Fixes

* conversion factor not set ([089c7d0](089c7d0a37))
* currency in coa import ([#34174](https://github.com/frappe/erpnext/issues/34174)) ([4d92d46](4d92d469e4))
* default date in Subcontracting reports ([5fce8e2](5fce8e2700))
* german translations ([#31732](https://github.com/frappe/erpnext/issues/31732)) ([88a781f](88a781fa43))
* incorrect acc depr amount if multiple FBs with straight line or manual method ([dda6bae](dda6baea3e))
* incorrect color in the BOM Stock Report ([001ed9e](001ed9e9ff))
* manual depr schedule ([971c072](971c0720e5))
* multiple pos conversion issue resolved ([db964e8](db964e8256))
* not able to repost gl entries ([ae0318e](ae0318ef74))
* permission error while calling get_work_order_items ([3ea90ee](3ea90ee5cb))
* pos return throwing amount greater than grand total ([9cd7b27](9cd7b27ce0))
* Remove missing DocField in fetch_from ([dc6ae46](dc6ae46d59))
* set `from_warehouse` and `to_warehouse` while mapping SE ([80e23d0](80e23d035e))
* **test:** use standalone method to fetch work orders from SO ([1719884](17198844c0))
* ui freeze on item selection in sales invoice ([1750ed4](1750ed4fb6))
* user shouldn't able to make item price for item template ([fb8e45d](fb8e45d3d9))
* zero division error while making LCV ([1859be6](1859be6fef))

### Performance Improvements

* fetch SLE's on demand and memoize ([db1f17e](db1f17e5bc))

### Reverts

* Revert "fix: Concurrency issues in Sales and Purchase returns" (#34202) ([9341d3e](9341d3e60e)), closes [#34202](https://github.com/frappe/erpnext/issues/34202) [#34019](https://github.com/frappe/erpnext/issues/34019)
2023-02-28 13:29:26 +00:00
ruthra kumar
60ab0d3981 Merge pull request #34238 from frappe/version-14-hotfix
chore: release v14
2023-02-28 18:57:57 +05:30
ruthra kumar
01b5ed91ba Merge pull request #34245 from frappe/mergify/bp/version-14-hotfix/pr-34241
fix: pos return throwing amount greater than grand total (backport #34241)
2023-02-28 18:32:48 +05:30
ruthra kumar
9cd7b27ce0 fix: pos return throwing amount greater than grand total
(cherry picked from commit 35c70f39fa)
2023-02-28 12:53:32 +00:00
rohitwaghchaure
521f19a044 Merge pull request #34244 from frappe/mergify/bp/version-14-hotfix/pr-34243
fix: default date in Subcontracting reports (backport #34243)
2023-02-28 18:06:34 +05:30
Rohit Waghchaure
5fce8e2700 fix: default date in Subcontracting reports
(cherry picked from commit dfddc4efc3)
2023-02-28 12:32:13 +00:00
Rohit Waghchaure
5e9f1dfbb3 fix: labels name
(cherry picked from commit a8445da02a)
2023-02-28 12:05:58 +00:00
Rohit Waghchaure
3ea1c73c07 test: added test cases
(cherry picked from commit 8e86553717)
2023-02-28 12:05:57 +00:00
Rohit Waghchaure
db033c6862 feat: adjust purchase receipt valuation rate as per purchase invoice rate
(cherry picked from commit eab775ef32)
2023-02-28 12:05:56 +00:00
Sagar Sharma
667ec983ec Merge pull request #34236 from frappe/mergify/bp/version-14-hotfix/pr-34060
fix: multiple Point of Sale conversion issue resolved (backport #34060)
2023-02-28 16:49:35 +05:30
Vishal
bbcd101613 chore: minor changes in pos_controller
(cherry picked from commit f18ae5856f)
2023-02-28 09:26:38 +00:00
Vishal
f812dbc524 chore: minor change
(cherry picked from commit a51bec0269)
2023-02-28 09:26:37 +00:00
Vishal
786eb97ab4 chore: minor changes added to code
(cherry picked from commit 3ebe7d861d)
2023-02-28 09:26:37 +00:00
Vishal
db964e8256 fix: multiple pos conversion issue resolved
(cherry picked from commit 1de531e56e)
2023-02-28 09:26:36 +00:00
ruthra kumar
3117758575 Merge pull request #34219 from frappe/mergify/bp/version-14-hotfix/pr-34207
fix: permission error while calling get_work_order_items (backport #34207)
2023-02-28 10:16:36 +05:30
Sagar Sharma
7797645583 Merge pull request #34226 from frappe/mergify/bp/version-14-hotfix/pr-34225
fix: set `from_warehouse` and `to_warehouse` while mapping SE (backport #34225)
2023-02-27 13:21:03 +05:30
s-aga-r
80e23d035e fix: set from_warehouse and to_warehouse while mapping SE
(cherry picked from commit c09a61f360)
2023-02-27 07:23:21 +00:00
mergify[bot]
4d92d469e4 fix: currency in coa import (#34174)
* fix: currency in coa import

(cherry picked from commit 19c0b7a523)

* chore: change column label

(cherry picked from commit e3c000d0be)

---------

Co-authored-by: vishnu <vishnuviswambaran2002@gmail.com>
2023-02-27 12:19:12 +05:30
Sagar Sharma
14c248cfbc Merge pull request #34223 from frappe/mergify/bp/version-14-hotfix/pr-34212
fix: Remove missing DocField in fetch_from (backport #34212)
2023-02-27 12:15:43 +05:30
Brian Pond
dc6ae46d59 fix: Remove missing DocField in fetch_from
(cherry picked from commit 83f3e317e1)
2023-02-27 06:24:24 +00:00
mergify[bot]
88a781fa43 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:45 +05:30
Sagar Sharma
dd0876da17 Merge pull request #34208 from frappe/mergify/bp/version-14-hotfix/pr-34206
fix: not able to repost gl entries (backport #34206)
2023-02-27 10:27:00 +05:30
Sagar Sharma
e93bc94f0b Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-34206 2023-02-27 10:26:45 +05:30
ruthra kumar
17198844c0 fix(test): use standalone method to fetch work orders from SO
(cherry picked from commit a11d3327df)
2023-02-27 04:33:17 +00:00
ruthra kumar
3ea90ee5cb fix: permission error while calling get_work_order_items
(cherry picked from commit b6bad728cd)
2023-02-27 04:33:17 +00:00
Anand Baburajan
a9b8187dd0 Merge pull request #34205 from AnandBaburajan/manual_asset_schedule
fix: asset manual depr schedule
2023-02-26 20:07:50 +05:30
anandbaburajan
d56ca011fe chore: refactor long if conditions 2023-02-26 15:03:16 +05:30
anandbaburajan
75386e3653 chore: should prepare schedule if not draft 2023-02-25 21:17:18 +05:30
anandbaburajan
dda6baea3e fix: incorrect acc depr amount if multiple FBs with straight line or manual method 2023-02-25 14:43:24 +05:30
Rohit Waghchaure
ae0318ef74 fix: not able to repost gl entries
(cherry picked from commit 7d10dd9ea8)
2023-02-24 15:41:25 +00:00
anandbaburajan
b0d670a51d chore: handle change in opening_accumulated_depreciation properly 2023-02-24 20:35:29 +05:30
Anand Baburajan
e0ad1a305e Merge branch 'version-14-hotfix' into manual_asset_schedule 2023-02-24 18:14:03 +05:30
rohitwaghchaure
dddbfba6da Merge pull request #34200 from frappe/mergify/bp/version-14-hotfix/pr-34199
fix: conversion factor not set (backport #34199)
2023-02-24 17:53:03 +05:30
Anand Baburajan
149043e8d4 Merge branch 'version-14-hotfix' into manual_asset_schedule 2023-02-24 16:48:43 +05:30
anandbaburajan
971c0720e5 fix: manual depr schedule 2023-02-24 16:38:39 +05:30
Frappe PR Bot
cb266cd1c6 chore(release): Bumped to Version 14.17.1
## [14.17.1](https://github.com/frappe/erpnext/compare/v14.17.0...v14.17.1) (2023-02-24)

### Reverts

* Revert "fix: Concurrency issues in Sales and Purchase returns" (#34202) ([3c2e21e](3c2e21e2ae)), closes [#34202](https://github.com/frappe/erpnext/issues/34202) [#34019](https://github.com/frappe/erpnext/issues/34019)
2023-02-24 10:07:56 +00:00
Deepesh Garg
d6d1238974 Merge pull request #34204 from frappe/mergify/bp/version-14/pr-34203
Revert "fix: Concurrency issues in Sales and Purchase returns" (backport #34202) (backport #34203)
2023-02-24 15:36:02 +05:30
Deepesh Garg
3c2e21e2ae Revert "fix: Concurrency issues in Sales and Purchase returns" (#34202)
Revert "fix: Concurrency issues in Sales and Purchase returns (#34019)"

This reverts commit a67284e96d.

(cherry picked from commit e26c6dc76b)
(cherry picked from commit 9341d3e60e)
2023-02-24 10:03:47 +00:00
Deepesh Garg
c6a46bc184 Merge pull request #34203 from frappe/mergify/bp/version-14-hotfix/pr-34202
Revert "fix: Concurrency issues in Sales and Purchase returns" (backport #34202)
2023-02-24 15:32:00 +05:30
Deepesh Garg
9341d3e60e Revert "fix: Concurrency issues in Sales and Purchase returns" (#34202)
Revert "fix: Concurrency issues in Sales and Purchase returns (#34019)"

This reverts commit a67284e96d.

(cherry picked from commit e26c6dc76b)
2023-02-24 09:58:40 +00:00
Rohit Waghchaure
089c7d0a37 fix: conversion factor not set
(cherry picked from commit 8e46aebc50)
2023-02-24 09:27:37 +00:00
rohitwaghchaure
230e345732 Merge pull request #34190 from frappe/mergify/bp/version-14-hotfix/pr-34189
fix: user shouldn't able to make item price for item template (backport #34189)
2023-02-24 09:23:22 +05:30
Rohit Waghchaure
fb8e45d3d9 fix: user shouldn't able to make item price for item template
(cherry picked from commit 6417ae0ee8)
2023-02-23 15:18:43 +00:00
rohitwaghchaure
b2582c56b7 Merge pull request #34177 from frappe/mergify/bp/version-14-hotfix/pr-34173
fix: incorrect color in the BOM Stock Report (backport #34173)
2023-02-23 20:47:24 +05:30
ruthra kumar
908d2f687e Merge pull request #34184 from frappe/mergify/bp/version-14-hotfix/pr-34022
perf: Gross Profit report will fetch SLE's on demand and memoize (backport #34022)
2023-02-23 12:54:25 +05:30
ruthra kumar
204f9a414f refactor: use docstatus from Delivery Note Item
(cherry picked from commit 88d888d9d0)
2023-02-23 06:26:15 +00:00
ruthra kumar
db1f17e5bc perf: fetch SLE's on demand and memoize
(cherry picked from commit 3e5691072a)
2023-02-23 06:26:15 +00:00
rohitwaghchaure
292f7c57c5 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-34173 2023-02-23 11:36:34 +05:30
rohitwaghchaure
b0103b85a0 Merge pull request #34181 from frappe/mergify/bp/version-14-hotfix/pr-34172
fix: zero division error while making LCV (backport #34172)
2023-02-23 11:25:03 +05:30
Rohit Waghchaure
1859be6fef fix: zero division error while making LCV
(cherry picked from commit 80e94a08cf)
2023-02-23 05:24:58 +00:00
ruthra kumar
b37695f2f3 Merge pull request #34180 from frappe/mergify/bp/version-14-hotfix/pr-34176
fix: ui freeze upon item selection in sales invoice (backport #34176)
2023-02-23 10:52:32 +05:30
ruthra kumar
1750ed4fb6 fix: ui freeze on item selection in sales invoice
(cherry picked from commit 6412583e98)
2023-02-23 05:06:51 +00:00
Rohit Waghchaure
001ed9e9ff fix: incorrect color in the BOM Stock Report
(cherry picked from commit a8f03ebf7f)
2023-02-23 04:25:45 +00:00
248 changed files with 4554 additions and 2334 deletions

View File

@@ -8,8 +8,9 @@ sudo apt update && sudo apt install redis-server libcups2-dev
pip install frappe-bench
githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}}
frappeuser=${FRAPPE_USER:-"frappe"}
frappebranch=${FRAPPE_BRANCH:-${GITHUB_BASE_REF:-${GITHUB_REF##*/}}}
frappebranch=${FRAPPE_BRANCH:-$githubbranch}
git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
@@ -56,7 +57,7 @@ sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
bench get-app payments
bench get-app payments --branch ${githubbranch%"-hotfix"}
bench get-app erpnext "${GITHUB_WORKSPACE}"
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi

View File

@@ -3,13 +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/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,12 +18,8 @@ erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/subcontracting @rohitwaghchaure @s-aga-r
erpnext/crm/ @NagariaHussain
erpnext/education/ @rutwikhdev
erpnext/projects/ @ruchamahabal
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure
erpnext/patches/ @deepeshgarg007
erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib
.github/ @ankush
.github/ @deepeshgarg007
pyproject.toml @ankush

View File

@@ -2,7 +2,7 @@ import inspect
import frappe
__version__ = "14.17.0"
__version__ = "14.22.1"
def get_default_company(user=None):

View File

@@ -18,7 +18,6 @@
"root_type",
"report_type",
"account_currency",
"inter_company_account",
"column_break1",
"parent_account",
"account_type",
@@ -34,15 +33,11 @@
{
"fieldname": "properties",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
"oldfieldtype": "Section Break"
},
{
"fieldname": "column_break0",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%"
},
{
@@ -53,9 +48,7 @@
"no_copy": 1,
"oldfieldname": "account_name",
"oldfieldtype": "Data",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
"reqd": 1
},
{
"fieldname": "account_number",
@@ -63,17 +56,13 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Account Number",
"read_only": 1,
"show_days": 1,
"show_seconds": 1
"read_only": 1
},
{
"default": "0",
"fieldname": "is_group",
"fieldtype": "Check",
"label": "Is Group",
"show_days": 1,
"show_seconds": 1
"label": "Is Group"
},
{
"fieldname": "company",
@@ -85,9 +74,7 @@
"options": "Company",
"read_only": 1,
"remember_last_selected_value": 1,
"reqd": 1,
"show_days": 1,
"show_seconds": 1
"reqd": 1
},
{
"fieldname": "root_type",
@@ -95,9 +82,7 @@
"in_standard_filter": 1,
"label": "Root Type",
"options": "\nAsset\nLiability\nIncome\nExpense\nEquity",
"read_only": 1,
"show_days": 1,
"show_seconds": 1
"read_only": 1
},
{
"fieldname": "report_type",
@@ -105,32 +90,18 @@
"in_standard_filter": 1,
"label": "Report Type",
"options": "\nBalance Sheet\nProfit and Loss",
"read_only": 1,
"show_days": 1,
"show_seconds": 1
"read_only": 1
},
{
"depends_on": "eval:doc.is_group==0",
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "inter_company_account",
"fieldtype": "Check",
"label": "Inter Company Account",
"show_days": 1,
"show_seconds": 1
"options": "Currency"
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%"
},
{
@@ -142,9 +113,7 @@
"oldfieldtype": "Link",
"options": "Account",
"reqd": 1,
"search_index": 1,
"show_days": 1,
"show_seconds": 1
"search_index": 1
},
{
"description": "Setting Account Type helps in selecting this Account in transactions.",
@@ -154,9 +123,7 @@
"label": "Account Type",
"oldfieldname": "account_type",
"oldfieldtype": "Select",
"options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary",
"show_days": 1,
"show_seconds": 1
"options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary"
},
{
"description": "Rate at which this tax is applied",
@@ -164,9 +131,7 @@
"fieldtype": "Float",
"label": "Rate",
"oldfieldname": "tax_rate",
"oldfieldtype": "Currency",
"show_days": 1,
"show_seconds": 1
"oldfieldtype": "Currency"
},
{
"description": "If the account is frozen, entries are allowed to restricted users.",
@@ -175,17 +140,13 @@
"label": "Frozen",
"oldfieldname": "freeze_account",
"oldfieldtype": "Select",
"options": "No\nYes",
"show_days": 1,
"show_seconds": 1
"options": "No\nYes"
},
{
"fieldname": "balance_must_be",
"fieldtype": "Select",
"label": "Balance must be",
"options": "\nDebit\nCredit",
"show_days": 1,
"show_seconds": 1
"options": "\nDebit\nCredit"
},
{
"fieldname": "lft",
@@ -194,9 +155,7 @@
"label": "Lft",
"print_hide": 1,
"read_only": 1,
"search_index": 1,
"show_days": 1,
"show_seconds": 1
"search_index": 1
},
{
"fieldname": "rgt",
@@ -205,9 +164,7 @@
"label": "Rgt",
"print_hide": 1,
"read_only": 1,
"search_index": 1,
"show_days": 1,
"show_seconds": 1
"search_index": 1
},
{
"fieldname": "old_parent",
@@ -215,33 +172,27 @@
"hidden": 1,
"label": "Old Parent",
"print_hide": 1,
"read_only": 1,
"show_days": 1,
"show_seconds": 1
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)",
"fieldname": "include_in_gross",
"fieldtype": "Check",
"label": "Include in gross",
"show_days": 1,
"show_seconds": 1
"label": "Include in gross"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disable",
"show_days": 1,
"show_seconds": 1
"label": "Disable"
}
],
"icon": "fa fa-money",
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2020-06-11 15:15:54.338622",
"modified": "2023-04-11 16:08:46.983677",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account",
@@ -301,5 +252,6 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}

View File

@@ -393,7 +393,13 @@ def update_account_number(name, account_name, account_number=None, from_descenda
if ancestors and not allow_independent_account_creation:
for ancestor in ancestors:
if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"):
old_name = frappe.db.get_value(
"Account",
{"account_number": old_acc_number, "account_name": old_acc_name, "company": ancestor},
"name",
)
if old_name:
# same account in parent company exists
allow_child_account_creation = _("Allow Account Creation Against Child Company")

View File

@@ -29,6 +29,7 @@ def create_charts(
"root_type",
"is_group",
"tax_rate",
"account_currency",
]:
account_number = cstr(child.get("account_number")).strip()
@@ -95,7 +96,17 @@ def identify_is_group(child):
is_group = child.get("is_group")
elif len(
set(child.keys())
- set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])
- set(
[
"account_name",
"account_type",
"root_type",
"is_group",
"tax_rate",
"account_number",
"account_currency",
]
)
):
is_group = 1
else:
@@ -185,6 +196,7 @@ def get_account_tree_from_existing_company(existing_company):
"root_type",
"tax_rate",
"account_number",
"account_currency",
],
order_by="lft, rgt",
)
@@ -267,6 +279,7 @@ def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=Fals
"root_type",
"is_group",
"tax_rate",
"account_currency",
]:
continue

View File

@@ -31,6 +31,7 @@
"determine_address_tax_category_from",
"column_break_19",
"add_taxes_from_item_tax_template",
"book_tax_discount_loss",
"print_settings",
"show_inclusive_tax_in_print",
"column_break_12",
@@ -181,6 +182,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"
@@ -347,6 +349,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",
@@ -354,7 +363,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-11-27 21:49:52.538655",
"modified": "2023-04-14 17:22:03.680886",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -118,6 +118,10 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
}
plaid_success(token, response) {
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.update_bank_account_ids', {
response: response,
}).then(() => {
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
});
}
};

View File

@@ -81,7 +81,7 @@ class BankClearance(Document):
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
loan_disbursements = (
query = (
frappe.qb.from_(loan_disbursement)
.select(
ConstantColumn("Loan Disbursement").as_("payment_document"),
@@ -90,17 +90,22 @@ class BankClearance(Document):
ConstantColumn(0).as_("debit"),
loan_disbursement.reference_number.as_("cheque_number"),
loan_disbursement.reference_date.as_("cheque_date"),
loan_disbursement.clearance_date.as_("clearance_date"),
loan_disbursement.disbursement_date.as_("posting_date"),
loan_disbursement.applicant.as_("against_account"),
)
.where(loan_disbursement.docstatus == 1)
.where(loan_disbursement.disbursement_date >= self.from_date)
.where(loan_disbursement.disbursement_date <= self.to_date)
.where(loan_disbursement.clearance_date.isnull())
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
.orderby(loan_disbursement.disbursement_date)
.orderby(loan_disbursement.name, order=frappe.qb.desc)
).run(as_dict=1)
)
if not self.include_reconciled_entries:
query = query.where(loan_disbursement.clearance_date.isnull())
loan_disbursements = query.run(as_dict=1)
loan_repayment = frappe.qb.DocType("Loan Repayment")
@@ -113,16 +118,19 @@ class BankClearance(Document):
ConstantColumn(0).as_("credit"),
loan_repayment.reference_number.as_("cheque_number"),
loan_repayment.reference_date.as_("cheque_date"),
loan_repayment.clearance_date.as_("clearance_date"),
loan_repayment.applicant.as_("against_account"),
loan_repayment.posting_date,
)
.where(loan_repayment.docstatus == 1)
.where(loan_repayment.clearance_date.isnull())
.where(loan_repayment.posting_date >= self.from_date)
.where(loan_repayment.posting_date <= self.to_date)
.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
)
if not self.include_reconciled_entries:
query = query.where(loan_repayment.clearance_date.isnull())
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
query = query.where((loan_repayment.repay_from_salary == 0))

View File

@@ -18,6 +18,10 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
onload: function (frm) {
// Set default filter dates
today = frappe.datetime.get_today()
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today;
frm.trigger('bank_account');
},
@@ -32,6 +36,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
refresh: function (frm) {
frm.disable_save();
frappe.require("bank-reconciliation-tool.bundle.js", () =>
frm.trigger("make_reconciliation_tool")
);
@@ -72,10 +77,12 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
})
});
},
after_save: function (frm) {
frm.trigger("make_reconciliation_tool");
frm.add_custom_button(__('Get Unreconciled Entries'), function() {
frm.trigger("make_reconciliation_tool");
});
frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
},
bank_account: function (frm) {
@@ -89,7 +96,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
r.account,
"account_currency",
(r) => {
frm.currency = r.account_currency;
frm.doc.account_currency = r.account_currency;
frm.trigger("render_chart");
}
);
@@ -155,19 +162,19 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
}
},
render_chart: frappe.utils.debounce((frm) => {
render_chart(frm) {
frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager(
{
$reconciliation_tool_cards: frm.get_field(
"reconciliation_tool_cards"
).$wrapper,
bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance,
frm.doc.bank_statement_closing_balance,
cleared_balance: frm.cleared_balance,
currency: frm.currency,
currency: frm.doc.account_currency,
}
);
}, 500),
},
render(frm) {
if (frm.doc.bank_account) {

View File

@@ -14,6 +14,7 @@
"to_reference_date",
"filter_by_reference_date",
"column_break_2",
"account_currency",
"account_opening_balance",
"bank_statement_closing_balance",
"section_break_1",
@@ -59,7 +60,7 @@
"fieldname": "account_opening_balance",
"fieldtype": "Currency",
"label": "Account Opening Balance",
"options": "Currency",
"options": "account_currency",
"read_only": 1
},
{
@@ -67,7 +68,7 @@
"fieldname": "bank_statement_closing_balance",
"fieldtype": "Currency",
"label": "Closing Balance",
"options": "Currency"
"options": "account_currency"
},
{
"fieldname": "section_break_1",
@@ -104,13 +105,20 @@
"fieldname": "filter_by_reference_date",
"fieldtype": "Check",
"label": "Filter by Reference Date"
},
{
"fieldname": "account_currency",
"fieldtype": "Link",
"hidden": 1,
"label": "Account Currency",
"options": "Currency"
}
],
"hide_toolbar": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-01-13 13:00:02.022919",
"modified": "2023-03-07 11:02:24.535714",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation Tool",

View File

@@ -10,7 +10,7 @@ from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import cint, flt
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_paid_amount
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
get_amounts_not_reflected_in_system,
get_entries,
@@ -28,7 +28,7 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
filters = []
filters.append(["bank_account", "=", bank_account])
filters.append(["docstatus", "=", 1])
filters.append(["unallocated_amount", ">", 0])
filters.append(["unallocated_amount", ">", 0.0])
if to_date:
filters.append(["date", "<=", to_date])
if from_date:
@@ -66,7 +66,7 @@ def get_account_balance(bank_account, till_date):
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
total_debit, total_credit = 0, 0
total_debit, total_credit = 0.0, 0.0
for d in data:
total_debit += flt(d.debit)
total_credit += flt(d.credit)
@@ -145,10 +145,8 @@ def create_journal_entry_bts(
accounts.append(
{
"account": second_account,
"credit_in_account_currency": bank_transaction.deposit if bank_transaction.deposit > 0 else 0,
"debit_in_account_currency": bank_transaction.withdrawal
if bank_transaction.withdrawal > 0
else 0,
"credit_in_account_currency": bank_transaction.deposit,
"debit_in_account_currency": bank_transaction.withdrawal,
"party_type": party_type,
"party": party,
}
@@ -158,10 +156,8 @@ def create_journal_entry_bts(
{
"account": company_account,
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal
if bank_transaction.withdrawal > 0
else 0,
"debit_in_account_currency": bank_transaction.deposit if bank_transaction.deposit > 0 else 0,
"credit_in_account_currency": bank_transaction.withdrawal,
"debit_in_account_currency": bank_transaction.deposit,
}
)
@@ -185,16 +181,22 @@ def create_journal_entry_bts(
journal_entry.insert()
journal_entry.submit()
if bank_transaction.deposit > 0:
if bank_transaction.deposit > 0.0:
paid_amount = bank_transaction.deposit
else:
paid_amount = bank_transaction.withdrawal
vouchers = json.dumps(
[{"payment_doctype": "Journal Entry", "payment_name": journal_entry.name, "amount": paid_amount}]
[
{
"payment_doctype": "Journal Entry",
"payment_name": journal_entry.name,
"amount": paid_amount,
}
]
)
return reconcile_vouchers(bank_transaction.name, vouchers)
return reconcile_vouchers(bank_transaction_name, vouchers)
@frappe.whitelist()
@@ -218,7 +220,7 @@ def create_payment_entry_bts(
as_dict=True,
)[0]
paid_amount = bank_transaction.unallocated_amount
payment_type = "Receive" if bank_transaction.deposit > 0 else "Pay"
payment_type = "Receive" if bank_transaction.deposit > 0.0 else "Pay"
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
company = frappe.get_value("Account", company_account, "company")
@@ -257,9 +259,15 @@ def create_payment_entry_bts(
payment_entry.submit()
vouchers = json.dumps(
[{"payment_doctype": "Payment Entry", "payment_name": payment_entry.name, "amount": paid_amount}]
[
{
"payment_doctype": "Payment Entry",
"payment_name": payment_entry.name,
"amount": paid_amount,
}
]
)
return reconcile_vouchers(bank_transaction.name, vouchers)
return reconcile_vouchers(bank_transaction_name, vouchers)
@frappe.whitelist()
@@ -341,59 +349,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
# updated clear date of all the vouchers based on the bank transaction
vouchers = json.loads(vouchers)
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
company_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
if transaction.unallocated_amount == 0:
frappe.throw(_("This bank transaction is already fully reconciled"))
total_amount = 0
for voucher in vouchers:
voucher["payment_entry"] = frappe.get_doc(voucher["payment_doctype"], voucher["payment_name"])
total_amount += get_paid_amount(
frappe._dict(
{
"payment_document": voucher["payment_doctype"],
"payment_entry": voucher["payment_name"],
}
),
transaction.currency,
company_account,
)
if total_amount > transaction.unallocated_amount:
frappe.throw(
_(
"The sum total of amounts of all selected vouchers should be less than the unallocated amount of the bank transaction"
)
)
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
for voucher in vouchers:
gl_entry = frappe.db.get_value(
"GL Entry",
dict(
account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"]
),
["credit_in_account_currency as credit", "debit_in_account_currency as debit"],
as_dict=1,
)
gl_amount, transaction_amount = (
(gl_entry.credit, transaction.deposit)
if gl_entry.credit > 0
else (gl_entry.debit, transaction.withdrawal)
)
allocated_amount = gl_amount if gl_amount >= transaction_amount else transaction_amount
transaction.append(
"payment_entries",
{
"payment_document": voucher["payment_entry"].doctype,
"payment_entry": voucher["payment_entry"].name,
"allocated_amount": allocated_amount,
},
)
transaction.save()
transaction.update_allocations()
transaction.add_payment_entries(vouchers)
return frappe.get_doc("Bank Transaction", bank_transaction_name)
@@ -412,9 +368,9 @@ def get_linked_payments(
bank_account = frappe.db.get_values(
"Bank Account", transaction.bank_account, ["account", "company"], as_dict=True
)[0]
(account, company) = (bank_account.account, bank_account.company)
(gl_account, company) = (bank_account.account, bank_account.company)
matching = check_matching(
account,
gl_account,
company,
transaction,
document_types,
@@ -424,7 +380,27 @@ def get_linked_payments(
from_reference_date,
to_reference_date,
)
return matching
return subtract_allocations(gl_account, matching)
def subtract_allocations(gl_account, vouchers):
"Look up & subtract any existing Bank Transaction allocations"
copied = []
for voucher in vouchers:
rows = get_total_allocated_amount(voucher[1], voucher[2])
amount = None
for row in rows:
if row["gl_account"] == gl_account:
amount = row["total"]
break
if amount:
l = list(voucher)
l[3] -= amount
copied.append(tuple(l))
else:
copied.append(voucher)
return copied
def check_matching(
@@ -438,6 +414,7 @@ def check_matching(
from_reference_date,
to_reference_date,
):
exact_match = True if "exact_match" in document_types else False
# combine all types of vouchers
subquery = get_queries(
bank_account,
@@ -449,10 +426,11 @@ def check_matching(
filter_by_reference_date,
from_reference_date,
to_reference_date,
exact_match,
)
filters = {
"amount": transaction.unallocated_amount,
"payment_type": "Receive" if transaction.deposit > 0 else "Pay",
"payment_type": "Receive" if transaction.deposit > 0.0 else "Pay",
"reference_no": transaction.reference_number,
"party_type": transaction.party_type,
"party": transaction.party,
@@ -461,7 +439,9 @@ def check_matching(
matching_vouchers = []
matching_vouchers.extend(get_loan_vouchers(bank_account, transaction, document_types, filters))
matching_vouchers.extend(
get_loan_vouchers(bank_account, transaction, document_types, filters, exact_match)
)
for query in subquery:
matching_vouchers.extend(
@@ -483,10 +463,10 @@ def get_queries(
filter_by_reference_date,
from_reference_date,
to_reference_date,
exact_match,
):
# get queries to get matching vouchers
amount_condition = "=" if "exact_match" in document_types else "<="
account_from_to = "paid_to" if transaction.deposit > 0 else "paid_from"
account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from"
queries = []
# get matching queries from all the apps
@@ -497,7 +477,7 @@ def get_queries(
company,
transaction,
document_types,
amount_condition,
exact_match,
account_from_to,
from_date,
to_date,
@@ -516,7 +496,7 @@ def get_matching_queries(
company,
transaction,
document_types,
amount_condition,
exact_match,
account_from_to,
from_date,
to_date,
@@ -526,8 +506,8 @@ def get_matching_queries(
):
queries = []
if "payment_entry" in document_types:
pe_amount_matching = get_pe_matching_query(
amount_condition,
query = get_pe_matching_query(
exact_match,
account_from_to,
transaction,
from_date,
@@ -536,11 +516,11 @@ def get_matching_queries(
from_reference_date,
to_reference_date,
)
queries.extend([pe_amount_matching])
queries.append(query)
if "journal_entry" in document_types:
je_amount_matching = get_je_matching_query(
amount_condition,
query = get_je_matching_query(
exact_match,
transaction,
from_date,
to_date,
@@ -548,34 +528,70 @@ def get_matching_queries(
from_reference_date,
to_reference_date,
)
queries.extend([je_amount_matching])
queries.append(query)
if transaction.deposit > 0 and "sales_invoice" in document_types:
si_amount_matching = get_si_matching_query(amount_condition)
queries.extend([si_amount_matching])
if transaction.deposit > 0.0 and "sales_invoice" in document_types:
query = get_si_matching_query(exact_match)
queries.append(query)
if transaction.withdrawal > 0:
if transaction.withdrawal > 0.0:
if "purchase_invoice" in document_types:
pi_amount_matching = get_pi_matching_query(amount_condition)
queries.extend([pi_amount_matching])
query = get_pi_matching_query(exact_match)
queries.append(query)
if "bank_transaction" in document_types:
query = get_bt_matching_query(exact_match, transaction)
queries.append(query)
return queries
def get_loan_vouchers(bank_account, transaction, document_types, filters):
def get_loan_vouchers(bank_account, transaction, document_types, filters, exact_match):
vouchers = []
amount_condition = True if "exact_match" in document_types else False
if transaction.withdrawal > 0 and "loan_disbursement" in document_types:
vouchers.extend(get_ld_matching_query(bank_account, amount_condition, filters))
if transaction.withdrawal > 0.0 and "loan_disbursement" in document_types:
vouchers.extend(get_ld_matching_query(bank_account, exact_match, filters))
if transaction.deposit > 0 and "loan_repayment" in document_types:
vouchers.extend(get_lr_matching_query(bank_account, amount_condition, filters))
if transaction.deposit > 0.0 and "loan_repayment" in document_types:
vouchers.extend(get_lr_matching_query(bank_account, exact_match, filters))
return vouchers
def get_ld_matching_query(bank_account, amount_condition, filters):
def get_bt_matching_query(exact_match, transaction):
# get matching bank transaction query
# find bank transactions in the same bank account with opposite sign
# same bank account must have same company and currency
field = "deposit" if transaction.withdrawal > 0.0 else "withdrawal"
return f"""
SELECT
(CASE WHEN reference_number = %(reference_no)s THEN 1 ELSE 0 END
+ CASE WHEN {field} = %(amount)s THEN 1 ELSE 0 END
+ CASE WHEN ( party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
+ CASE WHEN unallocated_amount = %(amount)s THEN 1 ELSE 0 END
+ 1) AS rank,
'Bank Transaction' AS doctype,
name,
unallocated_amount AS paid_amount,
reference_number AS reference_no,
date AS reference_date,
party,
party_type,
date AS posting_date,
currency
FROM
`tabBank Transaction`
WHERE
status != 'Reconciled'
AND name != '{transaction.name}'
AND bank_account = '{transaction.bank_account}'
AND {field} {'= %(amount)s' if exact_match else '> 0.0'}
"""
def get_ld_matching_query(bank_account, exact_match, filters):
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
matching_reference = loan_disbursement.reference_number == filters.get("reference_number")
matching_party = loan_disbursement.applicant_type == filters.get(
@@ -603,17 +619,17 @@ def get_ld_matching_query(bank_account, amount_condition, filters):
.where(loan_disbursement.disbursement_account == bank_account)
)
if amount_condition:
if exact_match:
query.where(loan_disbursement.disbursed_amount == filters.get("amount"))
else:
query.where(loan_disbursement.disbursed_amount <= filters.get("amount"))
query.where(loan_disbursement.disbursed_amount > 0.0)
vouchers = query.run(as_list=True)
return vouchers
def get_lr_matching_query(bank_account, amount_condition, filters):
def get_lr_matching_query(bank_account, exact_match, filters):
loan_repayment = frappe.qb.DocType("Loan Repayment")
matching_reference = loan_repayment.reference_number == filters.get("reference_number")
matching_party = loan_repayment.applicant_type == filters.get(
@@ -644,10 +660,10 @@ def get_lr_matching_query(bank_account, amount_condition, filters):
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
query = query.where((loan_repayment.repay_from_salary == 0))
if amount_condition:
if exact_match:
query.where(loan_repayment.amount_paid == filters.get("amount"))
else:
query.where(loan_repayment.amount_paid <= filters.get("amount"))
query.where(loan_repayment.amount_paid > 0.0)
vouchers = query.run()
@@ -655,7 +671,7 @@ def get_lr_matching_query(bank_account, amount_condition, filters):
def get_pe_matching_query(
amount_condition,
exact_match,
account_from_to,
transaction,
from_date,
@@ -665,7 +681,7 @@ def get_pe_matching_query(
to_reference_date,
):
# get matching payment entries query
if transaction.deposit > 0:
if transaction.deposit > 0.0:
currency_field = "paid_to_account_currency as currency"
else:
currency_field = "paid_from_account_currency as currency"
@@ -680,7 +696,8 @@ def get_pe_matching_query(
return f"""
SELECT
(CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END
+ CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
+ CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
+ CASE WHEN paid_amount = %(amount)s THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Payment Entry' as doctype,
name,
@@ -694,20 +711,19 @@ def get_pe_matching_query(
FROM
`tabPayment Entry`
WHERE
paid_amount {amount_condition} %(amount)s
AND docstatus = 1
docstatus = 1
AND payment_type IN (%(payment_type)s, 'Internal Transfer')
AND ifnull(clearance_date, '') = ""
AND {account_from_to} = %(bank_account)s
AND paid_amount {'= %(amount)s' if exact_match else '> 0.0'}
{filter_by_date}
{filter_by_reference_no}
order by{order_by}
"""
def get_je_matching_query(
amount_condition,
exact_match,
transaction,
from_date,
to_date,
@@ -719,7 +735,7 @@ def get_je_matching_query(
# We have mapping at the bank level
# So one bank could have both types of bank accounts like asset and liability
# So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type
cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
cr_or_dr = "credit" if transaction.withdrawal > 0.0 else "debit"
filter_by_date = f"AND je.posting_date between '{from_date}' and '{to_date}'"
order_by = " je.posting_date"
filter_by_reference_no = ""
@@ -731,26 +747,29 @@ def get_je_matching_query(
return f"""
SELECT
(CASE WHEN je.cheque_no=%(reference_no)s THEN 1 ELSE 0 END
+ CASE WHEN jea.{cr_or_dr}_in_account_currency = %(amount)s THEN 1 ELSE 0 END
+ 1) AS rank ,
'Journal Entry' as doctype,
'Journal Entry' AS doctype,
je.name,
jea.{cr_or_dr}_in_account_currency as paid_amount,
je.cheque_no as reference_no,
je.cheque_date as reference_date,
je.pay_to_recd_from as party,
jea.{cr_or_dr}_in_account_currency AS paid_amount,
je.cheque_no AS reference_no,
je.cheque_date AS reference_date,
je.pay_to_recd_from AS party,
jea.party_type,
je.posting_date,
jea.account_currency as currency
jea.account_currency AS currency
FROM
`tabJournal Entry Account` as jea
`tabJournal Entry Account` AS jea
JOIN
`tabJournal Entry` as je
`tabJournal Entry` AS je
ON
jea.parent = je.name
WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00')
je.docstatus = 1
AND je.voucher_type NOT IN ('Opening Entry')
AND (je.clearance_date IS NULL OR je.clearance_date='0000-00-00')
AND jea.account = %(bank_account)s
AND jea.{cr_or_dr}_in_account_currency {amount_condition} %(amount)s
AND jea.{cr_or_dr}_in_account_currency {'= %(amount)s' if exact_match else '> 0.0'}
AND je.docstatus = 1
{filter_by_date}
{filter_by_reference_no}
@@ -758,11 +777,12 @@ def get_je_matching_query(
"""
def get_si_matching_query(amount_condition):
# get matchin sales invoice query
def get_si_matching_query(exact_match):
# get matching sales invoice query
return f"""
SELECT
( CASE WHEN si.customer = %(party)s THEN 1 ELSE 0 END
( CASE WHEN si.customer = %(party)s THEN 1 ELSE 0 END
+ CASE WHEN sip.amount = %(amount)s THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Sales Invoice' as doctype,
si.name,
@@ -780,18 +800,20 @@ def get_si_matching_query(amount_condition):
`tabSales Invoice` as si
ON
sip.parent = si.name
WHERE (sip.clearance_date is null or sip.clearance_date='0000-00-00')
WHERE
si.docstatus = 1
AND (sip.clearance_date is null or sip.clearance_date='0000-00-00')
AND sip.account = %(bank_account)s
AND sip.amount {amount_condition} %(amount)s
AND si.docstatus = 1
AND sip.amount {'= %(amount)s' if exact_match else '> 0.0'}
"""
def get_pi_matching_query(amount_condition):
# get matching purchase invoice query
def get_pi_matching_query(exact_match):
# get matching purchase invoice query when they are also used as payment entries (is_paid)
return f"""
SELECT
( CASE WHEN supplier = %(party)s THEN 1 ELSE 0 END
+ CASE WHEN paid_amount = %(amount)s THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Purchase Invoice' as doctype,
name,
@@ -805,9 +827,9 @@ def get_pi_matching_query(amount_condition):
FROM
`tabPurchase Invoice`
WHERE
paid_amount {amount_condition} %(amount)s
AND docstatus = 1
docstatus = 1
AND is_paid = 1
AND ifnull(clearance_date, '') = ""
AND cash_bank_account = %(bank_account)s
AND cash_bank_account = %(bank_account)s
AND paid_amount {'= %(amount)s' if exact_match else '> 0.0'}
"""

View File

@@ -12,8 +12,13 @@ frappe.ui.form.on("Bank Transaction", {
};
});
},
bank_account: function(frm) {
refresh(frm) {
frm.add_custom_button(__('Unreconcile Transaction'), () => {
frm.call('remove_payment_entries')
.then( () => frm.refresh() );
});
},
bank_account: function (frm) {
set_bank_statement_filter(frm);
},
@@ -34,6 +39,7 @@ frappe.ui.form.on("Bank Transaction", {
"Journal Entry",
"Sales Invoice",
"Purchase Invoice",
"Bank Transaction",
];
}
});
@@ -49,7 +55,7 @@ const update_clearance_date = (frm, cdt, cdn) => {
frappe
.xcall(
"erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment",
{ doctype: cdt, docname: cdn }
{ doctype: cdt, docname: cdn, bt_name: frm.doc.name }
)
.then((e) => {
if (e == "success") {

View File

@@ -20,9 +20,11 @@
"currency",
"section_break_10",
"description",
"section_break_14",
"reference_number",
"column_break_10",
"transaction_id",
"transaction_type",
"section_break_14",
"payment_entries",
"section_break_18",
"allocated_amount",
@@ -190,11 +192,21 @@
"label": "Withdrawal",
"oldfieldname": "credit",
"options": "currency"
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"fieldname": "transaction_type",
"fieldtype": "Data",
"label": "Transaction Type",
"length": 50
}
],
"is_submittable": 1,
"links": [],
"modified": "2022-03-21 19:05:04.208222",
"modified": "2022-05-29 18:36:50.475964",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",
@@ -248,4 +260,4 @@
"states": [],
"title_field": "bank_account",
"track_changes": 1
}
}

View File

@@ -1,9 +1,6 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from functools import reduce
import frappe
from frappe.utils import flt
@@ -18,72 +15,137 @@ class BankTransaction(StatusUpdater):
self.clear_linked_payment_entries()
self.set_status()
_saving_flag = False
# nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
def on_update_after_submit(self):
self.update_allocations()
self.clear_linked_payment_entries()
self.set_status(update=True)
"Run on save(). Avoid recursion caused by multiple saves"
if not self._saving_flag:
self._saving_flag = True
self.clear_linked_payment_entries()
self.update_allocations()
self._saving_flag = False
def on_cancel(self):
self.clear_linked_payment_entries(for_cancel=True)
self.set_status(update=True)
def update_allocations(self):
"The doctype does not allow modifications after submission, so write to the db direct"
if self.payment_entries:
allocated_amount = reduce(
lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries]
)
allocated_amount = sum(p.allocated_amount for p in self.payment_entries)
else:
allocated_amount = 0
allocated_amount = 0.0
if allocated_amount:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount))
frappe.db.set_value(
self.doctype,
self.name,
"unallocated_amount",
abs(flt(self.withdrawal) - flt(self.deposit)) - flt(allocated_amount),
)
amount = abs(flt(self.withdrawal) - flt(self.deposit))
self.db_set("allocated_amount", flt(allocated_amount))
self.db_set("unallocated_amount", amount - flt(allocated_amount))
self.reload()
self.set_status(update=True)
else:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0)
frappe.db.set_value(
self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit))
)
def add_payment_entries(self, vouchers):
"Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
if 0.0 >= self.unallocated_amount:
frappe.throw(frappe._(f"Bank Transaction {self.name} is already fully reconciled"))
amount = self.deposit or self.withdrawal
if amount == self.allocated_amount:
frappe.db.set_value(self.doctype, self.name, "status", "Reconciled")
added = False
for voucher in vouchers:
# Can't add same voucher twice
found = False
for pe in self.payment_entries:
if (
pe.payment_document == voucher["payment_doctype"]
and pe.payment_entry == voucher["payment_name"]
):
found = True
if not found:
pe = {
"payment_document": voucher["payment_doctype"],
"payment_entry": voucher["payment_name"],
"allocated_amount": 0.0, # Temporary
}
child = self.append("payment_entries", pe)
added = True
# runs on_update_after_submit
if added:
self.save()
def allocate_payment_entries(self):
"""Refactored from bank reconciliation tool.
Non-zero allocations must be amended/cleared manually
Get the bank transaction amount (b) and remove as we allocate
For each payment_entry if allocated_amount == 0:
- get the amount already allocated against all transactions (t), need latest date
- get the voucher amount (from gl) (v)
- allocate (a = v - t)
- a = 0: should already be cleared, so clear & remove payment_entry
- 0 < a <= u: allocate a & clear
- 0 < a, a > u: allocate u
- 0 > a: Error: already over-allocated
- clear means: set the latest transaction date as clearance date
"""
gl_bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
remaining_amount = self.unallocated_amount
for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0:
unallocated_amount, should_clear, latest_transaction = get_clearance_details(
self, payment_entry
)
if 0.0 == unallocated_amount:
if should_clear:
latest_transaction.clear_linked_payment_entry(payment_entry)
self.db_delete_payment_entry(payment_entry)
elif remaining_amount <= 0.0:
self.db_delete_payment_entry(payment_entry)
elif 0.0 < unallocated_amount and unallocated_amount <= remaining_amount:
payment_entry.db_set("allocated_amount", unallocated_amount)
remaining_amount -= unallocated_amount
if should_clear:
latest_transaction.clear_linked_payment_entry(payment_entry)
elif 0.0 < unallocated_amount and unallocated_amount > remaining_amount:
payment_entry.db_set("allocated_amount", remaining_amount)
remaining_amount = 0.0
elif 0.0 > unallocated_amount:
self.db_delete_payment_entry(payment_entry)
frappe.throw(
frappe._(f"Voucher {payment_entry.payment_entry} is over-allocated by {unallocated_amount}")
)
self.reload()
def clear_linked_payment_entries(self, for_cancel=False):
def db_delete_payment_entry(self, payment_entry):
frappe.db.delete("Bank Transaction Payments", {"name": payment_entry.name})
@frappe.whitelist()
def remove_payment_entries(self):
for payment_entry in self.payment_entries:
if payment_entry.payment_document == "Sales Invoice":
self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
elif payment_entry.payment_document in get_doctypes_for_bank_reconciliation():
self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
self.remove_payment_entry(payment_entry)
# runs on_update_after_submit
self.save()
def clear_simple_entry(self, payment_entry, for_cancel=False):
if payment_entry.payment_document == "Payment Entry":
if (
frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type")
== "Internal Transfer"
):
if len(get_reconciled_bank_transactions(payment_entry)) < 2:
return
def remove_payment_entry(self, payment_entry):
"Clear payment entry and clearance"
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
self.remove(payment_entry)
clearance_date = self.date if not for_cancel else None
frappe.db.set_value(
payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", clearance_date
)
def clear_linked_payment_entries(self, for_cancel=False):
if for_cancel:
for payment_entry in self.payment_entries:
self.clear_linked_payment_entry(payment_entry, for_cancel)
else:
self.allocate_payment_entries()
def clear_sales_invoice(self, payment_entry, for_cancel=False):
clearance_date = self.date if not for_cancel else None
frappe.db.set_value(
"Sales Invoice Payment",
dict(parenttype=payment_entry.payment_document, parent=payment_entry.payment_entry),
"clearance_date",
clearance_date,
def clear_linked_payment_entry(self, payment_entry, for_cancel=False):
clearance_date = None if for_cancel else self.date
set_voucher_clearance(
payment_entry.payment_document, payment_entry.payment_entry, clearance_date, self
)
@@ -93,38 +155,112 @@ def get_doctypes_for_bank_reconciliation():
return frappe.get_hooks("bank_reconciliation_doctypes")
def get_reconciled_bank_transactions(payment_entry):
reconciled_bank_transactions = frappe.get_all(
"Bank Transaction Payments",
filters={"payment_entry": payment_entry.payment_entry},
fields=["parent"],
def get_clearance_details(transaction, payment_entry):
"""
There should only be one bank gle for a voucher.
Could be none for a Bank Transaction.
But if a JE, could affect two banks.
Should only clear the voucher if all bank gles are allocated.
"""
gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
bt_allocations = get_total_allocated_amount(
payment_entry.payment_document, payment_entry.payment_entry
)
return reconciled_bank_transactions
unallocated_amount = min(
transaction.unallocated_amount,
get_paid_amount(payment_entry, transaction.currency, gl_bank_account),
)
unmatched_gles = len(gles)
latest_transaction = transaction
for gle in gles:
if gle["gl_account"] == gl_bank_account:
if gle["amount"] <= 0.0:
frappe.throw(
frappe._(f"Voucher {payment_entry.payment_entry} value is broken: {gle['amount']}")
)
unmatched_gles -= 1
unallocated_amount = gle["amount"]
for a in bt_allocations:
if a["gl_account"] == gle["gl_account"]:
unallocated_amount = gle["amount"] - a["total"]
if frappe.utils.getdate(transaction.date) < a["latest_date"]:
latest_transaction = frappe.get_doc("Bank Transaction", a["latest_name"])
else:
# Must be a Journal Entry affecting more than one bank
for a in bt_allocations:
if a["gl_account"] == gle["gl_account"] and a["total"] == gle["amount"]:
unmatched_gles -= 1
return unallocated_amount, unmatched_gles == 0, latest_transaction
def get_total_allocated_amount(payment_entry):
return frappe.db.sql(
def get_related_bank_gl_entries(doctype, docname):
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
result = frappe.db.sql(
"""
SELECT
SUM(btp.allocated_amount) as allocated_amount,
bt.name
ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount,
gle.account AS gl_account
FROM
`tabBank Transaction Payments` as btp
`tabGL Entry` gle
LEFT JOIN
`tabBank Transaction` bt ON bt.name=btp.parent
`tabAccount` ac ON ac.name=gle.account
WHERE
btp.payment_document = %s
AND
btp.payment_entry = %s
AND
bt.docstatus = 1""",
(payment_entry.payment_document, payment_entry.payment_entry),
ac.account_type = 'Bank'
AND gle.voucher_type = %(doctype)s
AND gle.voucher_no = %(docname)s
AND is_cancelled = 0
""",
dict(doctype=doctype, docname=docname),
as_dict=True,
)
return result
def get_paid_amount(payment_entry, currency, bank_account):
def get_total_allocated_amount(doctype, docname):
"""
Gets the sum of allocations for a voucher on each bank GL account
along with the latest bank transaction name & date
NOTE: query may also include just saved vouchers/payments but with zero allocated_amount
"""
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
result = frappe.db.sql(
"""
SELECT total, latest_name, latest_date, gl_account FROM (
SELECT
ROW_NUMBER() OVER w AS rownum,
SUM(btp.allocated_amount) OVER(PARTITION BY ba.account) AS total,
FIRST_VALUE(bt.name) OVER w AS latest_name,
FIRST_VALUE(bt.date) OVER w AS latest_date,
ba.account AS gl_account
FROM
`tabBank Transaction Payments` btp
LEFT JOIN `tabBank Transaction` bt ON bt.name=btp.parent
LEFT JOIN `tabBank Account` ba ON ba.name=bt.bank_account
WHERE
btp.payment_document = %(doctype)s
AND btp.payment_entry = %(docname)s
AND bt.docstatus = 1
WINDOW w AS (PARTITION BY ba.account ORDER BY bt.date desc)
) temp
WHERE
rownum = 1
""",
dict(doctype=doctype, docname=docname),
as_dict=True,
)
for row in result:
# Why is this *sometimes* a byte string?
if isinstance(row["latest_name"], bytes):
row["latest_name"] = row["latest_name"].decode()
row["latest_date"] = frappe.utils.getdate(row["latest_date"])
return result
def get_paid_amount(payment_entry, currency, gl_bank_account):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
paid_amount_field = "paid_amount"
@@ -147,7 +283,7 @@ def get_paid_amount(payment_entry, currency, bank_account):
elif payment_entry.payment_document == "Journal Entry":
return frappe.db.get_value(
"Journal Entry Account",
{"parent": payment_entry.payment_entry, "account": bank_account},
{"parent": payment_entry.payment_entry, "account": gl_bank_account},
"sum(credit_in_account_currency)",
)
@@ -166,6 +302,12 @@ def get_paid_amount(payment_entry, currency, bank_account):
payment_entry.payment_document, payment_entry.payment_entry, "amount_paid"
)
elif payment_entry.payment_document == "Bank Transaction":
dep, wth = frappe.db.get_value(
"Bank Transaction", payment_entry.payment_entry, ("deposit", "withdrawal")
)
return abs(flt(wth) - flt(dep))
else:
frappe.throw(
"Please reconcile {0}: {1} manually".format(
@@ -174,18 +316,55 @@ def get_paid_amount(payment_entry, currency, bank_account):
)
@frappe.whitelist()
def unclear_reference_payment(doctype, docname):
if frappe.db.exists(doctype, docname):
doc = frappe.get_doc(doctype, docname)
if doctype == "Sales Invoice":
frappe.db.set_value(
"Sales Invoice Payment",
dict(parenttype=doc.payment_document, parent=doc.payment_entry),
"clearance_date",
None,
)
else:
frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None)
def set_voucher_clearance(doctype, docname, clearance_date, self):
if doctype in [
"Payment Entry",
"Journal Entry",
"Purchase Invoice",
"Expense Claim",
"Loan Repayment",
"Loan Disbursement",
]:
if (
doctype == "Payment Entry"
and frappe.db.get_value("Payment Entry", docname, "payment_type") == "Internal Transfer"
and len(get_reconciled_bank_transactions(doctype, docname)) < 2
):
return
frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
return doc.payment_entry
elif doctype == "Sales Invoice":
frappe.db.set_value(
"Sales Invoice Payment",
dict(parenttype=doctype, parent=docname),
"clearance_date",
clearance_date,
)
elif doctype == "Bank Transaction":
# For when a second bank transaction has fixed another, e.g. refund
bt = frappe.get_doc(doctype, docname)
if clearance_date:
vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}]
bt.add_payment_entries(vouchers)
else:
for pe in bt.payment_entries:
if pe.payment_document == self.doctype and pe.payment_entry == self.name:
bt.remove(pe)
bt.save()
break
def get_reconciled_bank_transactions(doctype, docname):
return frappe.get_all(
"Bank Transaction Payments",
filters={"payment_document": doctype, "payment_entry": docname},
pluck="parent",
)
@frappe.whitelist()
def unclear_reference_payment(doctype, docname, bt_name):
bt = frappe.get_doc("Bank Transaction", bt_name)
set_voucher_clearance(doctype, docname, None, bt)
return docname

View File

@@ -36,7 +36,7 @@ def validate_columns(data):
no_of_columns = max([len(d) for d in data])
if no_of_columns > 7:
if no_of_columns > 8:
frappe.throw(
_("More columns found than expected. Please compare the uploaded file with standard template"),
title=(_("Wrong Template")),
@@ -233,6 +233,7 @@ def build_forest(data):
is_group,
account_type,
root_type,
account_currency,
) = i
if not account_name:
@@ -253,6 +254,8 @@ def build_forest(data):
charts_map[account_name]["account_type"] = account_type
if root_type:
charts_map[account_name]["root_type"] = root_type
if account_currency:
charts_map[account_name]["account_currency"] = account_currency
path = return_parent(data, account_name)[::-1]
paths.append(path) # List of path is created
line_no += 1
@@ -315,20 +318,21 @@ def get_template(template_type):
"Is Group",
"Account Type",
"Root Type",
"Account Currency",
]
writer = UnicodeWriter()
writer.writerow(fields)
if template_type == "Blank Template":
for root_type in get_root_types():
writer.writerow(["", "", "", 1, "", root_type])
writer.writerow(["", "", "", "", 1, "", root_type])
for account in get_mandatory_group_accounts():
writer.writerow(["", "", "", 1, account, "Asset"])
writer.writerow(["", "", "", "", 1, account, "Asset"])
for account_type in get_mandatory_account_types():
writer.writerow(
["", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
)
else:
writer = get_sample_template(writer)

View File

@@ -211,8 +211,7 @@ class ExchangeRateRevaluation(Document):
# Handle Accounts with '0' balance in Account/Base Currency
for d in [x for x in account_details if x.zero_balance]:
# TODO: Set new balance in Base/Account currency
if d.balance > 0:
if d.balance != 0:
current_exchange_rate = new_exchange_rate = 0
new_balance_in_account_currency = 0 # this will be '0'
@@ -399,6 +398,9 @@ class ExchangeRateRevaluation(Document):
journal_entry_accounts = []
for d in accounts:
if not flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")):
continue
dr_or_cr = (
"debit_in_account_currency"
if d.get("balance_in_account_currency") > 0
@@ -448,7 +450,13 @@ class ExchangeRateRevaluation(Document):
}
)
journal_entry_accounts.append(
journal_entry.set("accounts", journal_entry_accounts)
journal_entry.set_amounts_in_company_currency()
journal_entry.set_total_debit_credit()
self.gain_loss_unbooked += journal_entry.difference - self.gain_loss_unbooked
journal_entry.append(
"accounts",
{
"account": unrealized_exchange_gain_loss_account,
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
@@ -460,10 +468,9 @@ class ExchangeRateRevaluation(Document):
"exchange_rate": 1,
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
},
)
journal_entry.set("accounts", journal_entry_accounts)
journal_entry.set_amounts_in_company_currency()
journal_entry.set_total_debit_credit()
journal_entry.save()
@@ -483,6 +490,8 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party):
conditions.append(gl.company == company)
conditions.append(gl.account == account)
conditions.append(gl.is_cancelled == 0)
conditions.append((gl.debit > 0) | (gl.credit > 0))
conditions.append((gl.debit_in_account_currency > 0) | (gl.credit_in_account_currency > 0))
if party_type:
conditions.append(gl.party_type == party_type)
if party:

View File

@@ -137,7 +137,8 @@
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
"options": "Finance Book",
"read_only": 1
},
{
"fieldname": "2_add_edit_gl_entries",
@@ -538,7 +539,7 @@
"idx": 176,
"is_submittable": 1,
"links": [],
"modified": "2023-01-17 12:53:53.280620",
"modified": "2023-03-01 14:58:59.286591",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -51,7 +51,7 @@ class JournalEntry(AccountsController):
self.validate_multi_currency()
self.set_amounts_in_company_currency()
self.validate_debit_credit_amount()
self.set_total_debit_credit()
# Do not validate while importing via data import
if not frappe.flags.in_import:
self.validate_total_debit_and_credit()
@@ -659,7 +659,6 @@ class JournalEntry(AccountsController):
frappe.throw(_("Row {0}: Both Debit and Credit values cannot be zero").format(d.idx))
def validate_total_debit_and_credit(self):
self.set_total_debit_credit()
if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
if self.difference:
frappe.throw(

View File

@@ -287,10 +287,6 @@ class TestJournalEntry(unittest.TestCase):
jv.submit()
def test_inter_company_jv(self):
frappe.db.set_value("Account", "Sales Expenses - _TC", "inter_company_account", 1)
frappe.db.set_value("Account", "Buildings - _TC", "inter_company_account", 1)
frappe.db.set_value("Account", "Sales Expenses - _TC1", "inter_company_account", 1)
frappe.db.set_value("Account", "Buildings - _TC1", "inter_company_account", 1)
jv = make_journal_entry(
"Sales Expenses - _TC",
"Buildings - _TC",

View File

@@ -245,8 +245,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));
@@ -973,29 +971,47 @@ frappe.ui.form.on('Payment Entry', {
},
callback: function(r, rt) {
if(r.message) {
var write_off_row = $.map(frm.doc["deductions"] || [], function(t) {
const write_off_row = $.map(frm.doc["deductions"] || [], function(t) {
return t.account==r.message[account] ? t : null; });
var row = [];
var difference_amount = flt(frm.doc.difference_amount,
const difference_amount = flt(frm.doc.difference_amount,
precision("difference_amount"));
if (!write_off_row.length && difference_amount) {
row = frm.add_child("deductions");
row.account = r.message[account];
row.cost_center = r.message["cost_center"];
} else {
row = write_off_row[0];
}
const add_deductions = (details) => {
if (!write_off_row.length && difference_amount) {
row = frm.add_child("deductions");
row.account = details[account];
row.cost_center = details["cost_center"];
} else {
row = write_off_row[0];
}
if (row) {
row.amount = flt(row.amount) + difference_amount;
} else {
frappe.msgprint(__("No gain or loss in the exchange rate"))
}
if (row) {
row.amount = flt(row.amount) + difference_amount;
} else {
frappe.msgprint(__("No gain or loss in the exchange rate"))
}
refresh_field("deductions");
};
refresh_field("deductions");
if (!r.message[account]) {
frappe.prompt({
label: __("Please Specify Account"),
fieldname: account,
fieldtype: "Link",
options: "Account",
get_query: () => ({
filters: {
company: frm.doc.company,
}
})
}, (values) => {
const details = Object.assign({}, r.message, values);
add_deductions(details);
}, __(frappe.unscrub(account)));
} else {
add_deductions(r.message);
}
frm.events.set_unallocated_amount(frm);
}

View File

@@ -416,7 +416,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
@@ -424,20 +424,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(invoice_payment_amount_map.items(), 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"))
@@ -472,6 +489,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"
@@ -1550,17 +1594,7 @@ def get_account_details(account, date, cost_center=None):
@frappe.whitelist()
def get_company_defaults(company):
fields = ["write_off_account", "exchange_gain_loss_account", "cost_center"]
ret = frappe.get_cached_value("Company", company, fields, as_dict=1)
for fieldname in fields:
if not ret[fieldname]:
frappe.throw(
_("Please set default {0} in Company {1}").format(
frappe.get_meta("Company").get_label(fieldname), company
)
)
return ret
return frappe.get_cached_value("Company", company, fields, as_dict=1)
def get_outstanding_on_journal_entry(name):
@@ -1642,7 +1676,14 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
@frappe.whitelist()
def get_payment_entry(
dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None
dt,
dn,
party_amount=None,
bank_account=None,
bank_amount=None,
party_type=None,
payment_type=None,
reference_date=None,
):
reference_doc = None
doc = frappe.get_doc(dt, dn)
@@ -1669,8 +1710,9 @@ def get_payment_entry(
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")
@@ -1678,6 +1720,7 @@ def get_payment_entry(
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))
@@ -1711,14 +1754,19 @@ def get_payment_entry(
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_cached_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:
@@ -1769,16 +1817,17 @@ def get_payment_entry(
if party_account and bank:
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
@@ -1889,20 +1938,28 @@ 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
is_multi_currency = party_account_currency != doc.company_currency
if doc.doctype in eligible_for_payments and has_payment_schedule:
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
@@ -1911,23 +1968,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,7 +5,7 @@ import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import (
@@ -256,10 +256,25 @@ class TestPaymentEntry(FrappeTestCase):
},
)
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()
@@ -269,6 +284,190 @@ class TestPaymentEntry(FrappeTestCase):
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",
@@ -839,24 +1038,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

@@ -272,4 +272,32 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
}
};
frappe.ui.form.on('Payment Reconciliation Allocation', {
allocated_amount: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
// filter invoice
let invoice = frm.doc.invoices.filter((x) => (x.invoice_number == row.invoice_number));
// filter payment
let payment = frm.doc.payments.filter((x) => (x.reference_name == row.reference_name));
frm.call({
doc: frm.doc,
method: 'calculate_difference_on_allocation_change',
args: {
payment_entry: payment,
invoice: invoice,
allocated_amount: row.allocated_amount
},
callback: (r) => {
if (r.message) {
row.difference_amount = r.message;
frm.refresh();
}
}
});
}
});
extend_cscript(cur_frm.cscript, new erpnext.accounts.PaymentReconciliationController({frm: cur_frm}));

View File

@@ -221,15 +221,27 @@ class PaymentReconciliation(Document):
def get_difference_amount(self, payment_entry, invoice, allocated_amount):
difference_amount = 0
if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get(
"exchange_rate", 1
):
allocated_amount_in_ref_rate = payment_entry.get("exchange_rate", 1) * allocated_amount
allocated_amount_in_inv_rate = invoice.get("exchange_rate", 1) * allocated_amount
difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
if frappe.get_cached_value(
"Account", self.receivable_payable_account, "account_currency"
) != frappe.get_cached_value("Company", self.company, "default_currency"):
if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get(
"exchange_rate", 1
):
allocated_amount_in_ref_rate = payment_entry.get("exchange_rate", 1) * allocated_amount
allocated_amount_in_inv_rate = invoice.get("exchange_rate", 1) * allocated_amount
difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
return difference_amount
@frappe.whitelist()
def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry)
invoice[0]["exchange_rate"] = invoice_exchange_map.get(invoice[0].get("invoice_number"))
new_difference_amount = self.get_difference_amount(
payment_entry[0], invoice[0], allocated_amount
)
return new_difference_amount
@frappe.whitelist()
def allocate_entries(self, args):
self.validate_entries()

View File

@@ -5,7 +5,7 @@ import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, nowdate
from erpnext import get_default_cost_center
@@ -349,6 +349,11 @@ class TestPaymentReconciliation(FrappeTestCase):
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Difference amount should not be calculated for base currency accounts
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
si.reload()
@@ -390,6 +395,11 @@ class TestPaymentReconciliation(FrappeTestCase):
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Difference amount should not be calculated for base currency accounts
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
# check PR tool output
@@ -414,6 +424,11 @@ class TestPaymentReconciliation(FrappeTestCase):
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Difference amount should not be calculated for base currency accounts
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
# assert outstanding
@@ -450,6 +465,11 @@ class TestPaymentReconciliation(FrappeTestCase):
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Difference amount should not be calculated for base currency accounts
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
self.assertEqual(pr.get("invoices"), [])
@@ -824,6 +844,52 @@ class TestPaymentReconciliation(FrappeTestCase):
payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
self.assertCountEqual(payment_vouchers, [je2.name, pe2.name])
@change_settings(
"Accounts Settings",
{
"allow_multi_currency_invoices_against_single_party_account": 1,
},
)
def test_no_difference_amount_for_base_currency_accounts(self):
# Make Sale Invoice
si = self.create_sales_invoice(
qty=1, rate=1, posting_date=nowdate(), do_not_save=True, do_not_submit=True
)
si.customer = self.customer
si.currency = "EUR"
si.conversion_rate = 85
si.debit_to = self.debit_to
si.save().submit()
# Make payment using Payment Entry
pe1 = create_payment_entry(
company=self.company,
payment_type="Receive",
party_type="Customer",
party=self.customer,
paid_from=self.debit_to,
paid_to=self.bank,
paid_amount=100,
)
pe1.save()
pe1.submit()
pr = self.create_payment_reconciliation()
pr.party = self.customer
pr.receivable_payable_account = self.debit_to
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.invoices]
payments = [pr.payments[0].as_dict()]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
self.assertEqual(pr.allocation[0].allocated_amount, 85)
self.assertEqual(pr.allocation[0].difference_amount, 0)
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -495,26 +495,28 @@ def get_amount(ref_doc, payment_account=None):
"""get amount based on doctype"""
dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.rounded_total) - flt(ref_doc.advance_paid)
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if ref_doc.party_account_currency == ref_doc.currency:
grand_total = flt(ref_doc.outstanding_amount)
else:
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
if not ref_doc.get("is_pos"):
if ref_doc.party_account_currency == ref_doc.currency:
grand_total = flt(ref_doc.outstanding_amount)
else:
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
elif dt == "Sales Invoice":
for pay in ref_doc.payments:
if pay.type == "Phone" and pay.account == payment_account:
grand_total = pay.amount
break
elif dt == "POS Invoice":
for pay in ref_doc.payments:
if pay.type == "Phone" and pay.account == payment_account:
grand_total = pay.amount
break
elif dt == "Fees":
grand_total = ref_doc.outstanding_amount
if grand_total > 0:
return grand_total
else:
frappe.throw(_("Payment Entry is already created"))

View File

@@ -6,6 +6,7 @@ import unittest
import frappe
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.setup.utils import get_exchange_rate
@@ -45,7 +46,10 @@ class TestPaymentRequest(unittest.TestCase):
frappe.get_doc(method).insert(ignore_permissions=True)
def test_payment_request_linkings(self):
so_inr = make_sales_order(currency="INR")
so_inr = make_sales_order(currency="INR", do_not_save=True)
so_inr.disable_rounded_total = 1
so_inr.save()
pr = make_payment_request(
dt="Sales Order",
dn=so_inr.name,
@@ -71,6 +75,29 @@ class TestPaymentRequest(unittest.TestCase):
self.assertEqual(pr.reference_name, si_usd.name)
self.assertEqual(pr.currency, "USD")
def test_payment_entry_against_purchase_invoice(self):
si_usd = make_purchase_invoice(
customer="_Test Supplier USD",
debit_to="_Test Payable USD - _TC",
currency="USD",
conversion_rate=50,
)
pr = make_payment_request(
dt="Purchase Invoice",
dn=si_usd.name,
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
submit_doc=1,
return_doc=1,
)
pe = pr.create_payment_entry()
pr.load_from_db()
self.assertEqual(pr.status, "Paid")
def test_payment_entry(self):
frappe.db.set_value(
"Company", "_Test Company", "exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"

View File

@@ -112,7 +112,8 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
party_type: "Customer",
account: this.frm.doc.debit_to,
price_list: this.frm.doc.selling_price_list,
pos_profile: pos_profile
pos_profile: pos_profile,
company_address: this.frm.doc.company_address
}, () => {
this.apply_pricing_rule();
});

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 {}."
@@ -650,7 +650,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"
):
@@ -674,7 +674,7 @@ def get_bin_qty(item_code, warehouse):
def get_pos_reserved_qty(item_code, warehouse):
reserved_qty = frappe.db.sql(
"""select sum(p_item.qty) as qty
"""select sum(p_item.stock_qty) as qty
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
where p.name = p_item.parent
and ifnull(p.consolidated_invoice, '') = ''

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

@@ -23,7 +23,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 }}."
@@ -86,6 +86,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,
@@ -153,7 +154,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]],
)
@@ -176,7 +177,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:
@@ -225,7 +226,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

@@ -82,7 +82,11 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
if(doc.docstatus == 1 && doc.outstanding_amount != 0
&& !(doc.is_return && doc.return_against) && !doc.on_hold) {
this.frm.add_custom_button(__('Payment'), this.make_payment_entry, __('Create'));
this.frm.add_custom_button(
__('Payment'),
() => this.make_payment_entry(),
__('Create')
);
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
}

View File

@@ -118,6 +118,7 @@
"paid_amount",
"advances_section",
"allocate_advances_automatically",
"only_include_allocated_payments",
"get_advances",
"advances",
"advance_tax",
@@ -1550,17 +1551,24 @@
"fieldname": "named_place",
"fieldtype": "Data",
"label": "Named Place"
},
{
"default": "0",
"depends_on": "allocate_advances_automatically",
"description": "Advance payments allocated against orders will only be fetched",
"fieldname": "only_include_allocated_payments",
"fieldtype": "Check",
"label": "Only Include Allocated Payments"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2023-01-28 19:18:56.586321",
"modified": "2023-04-03 22:57:14.074982",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [

View File

@@ -117,7 +117,7 @@ class PurchaseInvoice(BuyingController):
self.validate_expense_account()
self.set_against_expense_account()
self.validate_write_off_account()
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items")
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount")
self.create_remarks()
self.set_status()
self.validate_purchase_receipt_if_update_stock()
@@ -232,7 +232,7 @@ class PurchaseInvoice(BuyingController):
)
if (
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate"))
and not self.is_return
and not self.is_internal_supplier
):
@@ -581,6 +581,7 @@ class PurchaseInvoice(BuyingController):
self.make_supplier_gl_entry(gl_entries)
self.make_item_gl_entries(gl_entries)
self.make_precision_loss_gl_entry(gl_entries)
if self.check_asset_cwip_enabled():
self.get_asset_gl_entry(gl_entries)
@@ -975,6 +976,28 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount, item.precision("item_tax_amount")
)
def make_precision_loss_gl_entry(self, gl_entries):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
self.company, "Purchase Invoice", self.name
)
precision_loss = self.get("base_net_total") - flt(
self.get("net_total") * self.conversion_rate, self.precision("net_total")
)
if precision_loss:
gl_entries.append(
self.get_gl_dict(
{
"account": round_off_account,
"against": self.supplier,
"credit": precision_loss,
"cost_center": self.cost_center or round_off_cost_center,
"remarks": _("Net total calculation precision loss"),
}
)
)
def get_asset_gl_entry(self, gl_entries):
arbnb_account = self.get_company_default("asset_received_but_not_billed")
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
@@ -1485,11 +1508,17 @@ class PurchaseInvoice(BuyingController):
if po_details:
updated_pr += update_billed_amount_based_on_po(po_details, update_modified)
adjust_incoming_rate = frappe.db.get_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
)
for pr in set(updated_pr):
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
pr_doc = frappe.get_doc("Purchase Receipt", pr)
update_billing_percentage(pr_doc, update_modified=update_modified)
update_billing_percentage(
pr_doc, update_modified=update_modified, adjust_incoming_rate=adjust_incoming_rate
)
def get_pr_details_billed_amt(self):
# Get billed amount based on purchase receipt item reference (pr_detail) in purchase invoice

View File

@@ -1523,6 +1523,94 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
company.enable_provisional_accounting_for_non_stock_items = 0
company.save()
def test_adjust_incoming_rate(self):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
frappe.db.set_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1
)
# Increase the cost of the item
pr = make_purchase_receipt(qty=1, rate=100)
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 100)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.rate = 150
pi.save()
pi.submit()
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 150)
# Reduce the cost of the item
pr = make_purchase_receipt(qty=1, rate=100)
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 100)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.rate = 50
pi.save()
pi.submit()
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 50)
frappe.db.set_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0
)
# Don't adjust incoming rate
pr = make_purchase_receipt(qty=1, rate=100)
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 100)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.rate = 50
pi.save()
pi.submit()
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 100)
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
def test_item_less_defaults(self):
pi = frappe.new_doc("Purchase Invoice")

View File

@@ -93,9 +93,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
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) {
@@ -1047,7 +1050,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

@@ -32,9 +32,6 @@
"cost_center",
"dimension_col_break",
"project",
"column_break_27",
"campaign",
"source",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -123,6 +120,7 @@
"account_for_change_amount",
"advances_section",
"allocate_advances_automatically",
"only_include_allocated_payments",
"get_advances",
"advances",
"write_off_section",
@@ -203,7 +201,9 @@
"more_information",
"status",
"inter_company_invoice_reference",
"campaign",
"represents_company",
"source",
"customer_group",
"col_break23",
"is_internal_customer",
@@ -2083,10 +2083,6 @@
"fieldname": "company_addr_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_27",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_52",
"fieldtype": "Column Break"
@@ -2131,6 +2127,13 @@
"label": "Repost Required",
"no_copy": 1,
"read_only": 1
},
{
"depends_on": "allocate_advances_automatically",
"description": "Advance payments allocated against orders will only be fetched",
"fieldname": "only_include_allocated_payments",
"fieldtype": "Check",
"label": "Only Include Allocated Payments"
}
],
"icon": "fa fa-file-text",
@@ -2143,11 +2146,10 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2022-11-07 16:02:07.972258",
"modified": "2023-04-03 22:55:14.206473",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [

View File

@@ -145,7 +145,7 @@ class SalesInvoice(SellingController):
self.set_against_income_account()
self.validate_time_sheets_are_submitted()
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount")
if not self.is_return:
self.validate_serial_numbers()
else:

View File

@@ -32,6 +32,16 @@ from erpnext import get_company_currency
from erpnext.accounts.utils import get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
SALES_TRANSACTION_TYPES = {
"Quotation",
"Sales Order",
"Delivery Note",
"Sales Invoice",
"POS Invoice",
}
TRANSACTION_TYPES = PURCHASE_TRANSACTION_TYPES | SALES_TRANSACTION_TYPES
class DuplicatePartyAccountError(frappe.ValidationError):
pass
@@ -124,12 +134,6 @@ def _get_party_details(
set_other_values(party_details, party, party_type)
set_price_list(party_details, party, party_type, price_list, pos_profile)
party_details["tax_category"] = get_address_tax_category(
party.get("tax_category"),
party_address,
shipping_address if party_type != "Supplier" else party_address,
)
tax_template = set_taxes(
party.name,
party_type,
@@ -170,6 +174,9 @@ def _get_party_details(
party_type, party.name, "tax_withholding_category"
)
if not party_details.get("tax_category") and pos_profile:
party_details["tax_category"] = frappe.get_value("POS Profile", pos_profile, "tax_category")
return party_details
@@ -211,20 +218,10 @@ def set_address_details(
else:
party_details.update(get_company_address(company))
if doctype and doctype in [
"Delivery Note",
"Sales Invoice",
"Sales Order",
"Quotation",
"POS Invoice",
]:
if party_details.company_address:
party_details.update(
get_fetch_values(doctype, "company_address", party_details.company_address)
)
get_regional_address_details(party_details, doctype, company)
if doctype in SALES_TRANSACTION_TYPES and party_details.company_address:
party_details.update(get_fetch_values(doctype, "company_address", party_details.company_address))
elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
if doctype in PURCHASE_TRANSACTION_TYPES:
if shipping_address:
party_details.update(
shipping_address=shipping_address,
@@ -250,9 +247,21 @@ def set_address_details(
**get_fetch_values(doctype, "shipping_address", party_details.billing_address)
)
party_address, shipping_address = (
party_details.get(billing_address_field),
party_details.shipping_address_name,
)
party_details["tax_category"] = get_address_tax_category(
party.get("tax_category"),
party_address,
shipping_address if party_type != "Supplier" else party_address,
)
if doctype in TRANSACTION_TYPES:
get_regional_address_details(party_details, doctype, company)
return party_details.get(billing_address_field), party_details.shipping_address_name
return party_address, shipping_address
@erpnext.allow_regional

View File

@@ -859,7 +859,7 @@ class ReceivablePayableReport(object):
)
else:
self.qb_selection_filter.append(
self.ple[dimension.fieldname] == self.filters[dimension.fieldname]
self.ple[dimension.fieldname].isin(self.filters[dimension.fieldname])
)
def is_invoice(self, ple):

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

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import getdate, nowdate
@@ -91,4 +92,65 @@ def get_entries(filters):
as_list=1,
)
return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate()))
# Loan Disbursement
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
query = (
frappe.qb.from_(loan_disbursement)
.select(
ConstantColumn("Loan Disbursement").as_("payment_document_type"),
loan_disbursement.name.as_("payment_entry"),
loan_disbursement.disbursement_date.as_("posting_date"),
loan_disbursement.reference_number.as_("cheque_no"),
loan_disbursement.clearance_date.as_("clearance_date"),
loan_disbursement.applicant.as_("against"),
-loan_disbursement.disbursed_amount.as_("amount"),
)
.where(loan_disbursement.docstatus == 1)
.where(loan_disbursement.disbursement_date >= filters["from_date"])
.where(loan_disbursement.disbursement_date <= filters["to_date"])
.where(loan_disbursement.disbursement_account == filters["account"])
.orderby(loan_disbursement.disbursement_date, order=frappe.qb.desc)
.orderby(loan_disbursement.name, order=frappe.qb.desc)
)
if filters.get("from_date"):
query = query.where(loan_disbursement.disbursement_date >= filters["from_date"])
if filters.get("to_date"):
query = query.where(loan_disbursement.disbursement_date <= filters["to_date"])
loan_disbursements = query.run(as_list=1)
# Loan Repayment
loan_repayment = frappe.qb.DocType("Loan Repayment")
query = (
frappe.qb.from_(loan_repayment)
.select(
ConstantColumn("Loan Repayment").as_("payment_document_type"),
loan_repayment.name.as_("payment_entry"),
loan_repayment.posting_date.as_("posting_date"),
loan_repayment.reference_number.as_("cheque_no"),
loan_repayment.clearance_date.as_("clearance_date"),
loan_repayment.applicant.as_("against"),
loan_repayment.amount_paid.as_("amount"),
)
.where(loan_repayment.docstatus == 1)
.where(loan_repayment.posting_date >= filters["from_date"])
.where(loan_repayment.posting_date <= filters["to_date"])
.where(loan_repayment.payment_account == filters["account"])
.orderby(loan_repayment.posting_date, order=frappe.qb.desc)
.orderby(loan_repayment.name, order=frappe.qb.desc)
)
if filters.get("from_date"):
query = query.where(loan_repayment.posting_date >= filters["from_date"])
if filters.get("to_date"):
query = query.where(loan_repayment.posting_date <= filters["to_date"])
loan_repayments = query.run(as_list=1)
return sorted(
journal_entries + payment_entries + loan_disbursements + loan_repayments,
key=lambda k: k[2] or getdate(nowdate()),
)

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

@@ -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 || data[i].account_currency) %}</td>
<td style="text-align: right">
{%= format_currency(data[i].credit, filters.presentation_currency || data[i].account_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', "");
}

View File

@@ -24,7 +24,6 @@ class TestGeneralLedger(FrappeTestCase):
"root_type": "Asset",
"report_type": "Balance Sheet",
"account_currency": "USD",
"inter_company_account": 0,
"parent_account": "Bank Accounts - _TC",
"account_type": "Bank",
"doctype": "Account",

View File

@@ -395,6 +395,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)
@@ -404,7 +405,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()
@@ -501,7 +501,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:
@@ -633,7 +640,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:
@@ -651,7 +658,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
)
@@ -667,15 +674,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)
@@ -737,6 +741,8 @@ 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))
@@ -940,24 +946,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

@@ -381,3 +381,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

@@ -37,6 +37,29 @@ function get_filters() {
});
}
},
{
"fieldname":"party_type",
"label": __("Party Type"),
"fieldtype": "Link",
"options": "Party Type",
"default": "",
on_change: function() {
frappe.query_report.set_filter_value('party', "");
}
},
{
"fieldname":"party",
"label": __("Party"),
"fieldtype": "MultiSelectList",
get_data: function(txt) {
if (!frappe.query_report.filters) return;
let party_type = frappe.query_report.get_filter_value('party_type');
if (!party_type) return;
return frappe.db.get_link_options(party_type, txt);
},
},
{
"fieldname":"voucher_no",
"label": __("Voucher No"),
@@ -49,6 +72,20 @@ function get_filters() {
"fieldtype": "Data",
"width": 100,
},
{
"fieldname":"include_account_currency",
"label": __("Include Account Currency"),
"fieldtype": "Check",
"width": 100,
},
{
"fieldname":"group_party",
"label": __("Group by Party"),
"fieldtype": "Check",
"width": 100,
},
]
return filters;

View File

@@ -17,34 +17,26 @@ class PaymentLedger(object):
self.ple = qb.DocType("Payment Ledger Entry")
def init_voucher_dict(self):
if self.voucher_amount:
s = set()
# build a set of unique vouchers
# for each ple, using group_by_key to create a key and assign it to +/- list
for ple in self.voucher_amount:
key = (ple.voucher_type, ple.voucher_no, ple.party)
s.add(key)
group_by_key = None
if not self.filters.group_party:
group_by_key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
else:
group_by_key = (ple.party_type, ple.party)
# for each unique vouchers, initialize +/- list
for key in s:
self.voucher_dict[key] = frappe._dict(increase=list(), decrease=list())
# for each ple, using against voucher and amount, assign it to +/- list
# group by against voucher
for ple in self.voucher_amount:
against_key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
target = None
if self.voucher_dict.get(against_key):
if ple.amount > 0:
target = self.voucher_dict.get(against_key).increase
else:
target = self.voucher_dict.get(against_key).decrease
if ple.amount > 0:
target = self.voucher_dict.setdefault(group_by_key, {}).setdefault("increase", [])
else:
target = self.voucher_dict.setdefault(group_by_key, {}).setdefault("decrease", [])
# this if condition will lose unassigned ple entries(against_voucher doc doesn't have ple)
# need to somehow include the stray entries as well.
if target is not None:
entry = frappe._dict(
company=ple.company,
posting_date=ple.posting_date,
account=ple.account,
party_type=ple.party_type,
party=ple.party,
@@ -66,10 +58,10 @@ class PaymentLedger(object):
for value in self.voucher_dict.values():
voucher_data = []
if value.increase != []:
voucher_data.extend(value.increase)
if value.decrease != []:
voucher_data.extend(value.decrease)
if value.get("increase"):
voucher_data.extend(value.get("increase"))
if value.get("decrease"):
voucher_data.extend(value.get("decrease"))
if voucher_data:
# balance row
@@ -117,6 +109,12 @@ class PaymentLedger(object):
if self.filters.against_voucher_no:
self.conditions.append(self.ple.against_voucher_no == self.filters.against_voucher_no)
if self.filters.party_type:
self.conditions.append(self.ple.party_type == self.filters.party_type)
if self.filters.party:
self.conditions.append(self.ple.party.isin(self.filters.party))
def get_data(self):
ple = self.ple
@@ -134,7 +132,13 @@ class PaymentLedger(object):
def get_columns(self):
options = None
self.columns.append(
dict(label=_("Company"), fieldname="company", fieldtype="data", options=options, width="100")
dict(
label=_("Posting Date"),
fieldname="posting_date",
fieldtype="Date",
options=options,
width="100",
)
)
self.columns.append(
@@ -160,7 +164,11 @@ class PaymentLedger(object):
)
self.columns.append(
dict(
label=_("Voucher No"), fieldname="voucher_no", fieldtype="data", options=options, width="100"
label=_("Voucher No"),
fieldname="voucher_no",
fieldtype="Dynamic Link",
options="voucher_type",
width="100",
)
)
self.columns.append(
@@ -176,8 +184,8 @@ class PaymentLedger(object):
dict(
label=_("Against Voucher No"),
fieldname="against_voucher_no",
fieldtype="data",
options=options,
fieldtype="Dynamic Link",
options="against_voucher_type",
width="100",
)
)
@@ -209,7 +217,7 @@ class PaymentLedger(object):
self.get_columns()
self.get_data()
# initialize dictionary and group using against voucher
# initialize dictionary and group using key
self.init_voucher_dict()
# convert dictionary to list and add balance rows

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")
)
@@ -218,7 +215,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 +225,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 +242,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 +275,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 +306,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

@@ -451,12 +451,6 @@ def reconcile_against_document(args): # nosemgrep
else:
update_reference_in_payment_entry(entry, doc, do_not_save=True)
if doc.doctype == "Journal Entry":
try:
doc.validate_total_debit_and_credit()
except Exception as validation_exception:
raise frappe.ValidationError(_(f"Validation Error for {doc.name}")) from validation_exception
doc.save(ignore_permissions=True)
# re-submit advance entry
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)

View File

@@ -302,10 +302,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
@@ -473,6 +469,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)
@@ -567,19 +566,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

@@ -81,6 +81,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,
@@ -527,7 +530,7 @@
"table_fieldname": "accounts"
}
],
"modified": "2023-01-25 17:45:48.649543",
"modified": "2023-03-30 15:07:41.542374",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
@@ -571,4 +574,4 @@
"states": [],
"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:
@@ -255,17 +294,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)
)
@@ -281,7 +345,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:
@@ -296,12 +363,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
@@ -320,7 +395,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(
@@ -440,28 +519,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):
@@ -555,9 +643,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")):
@@ -565,6 +651,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
@@ -860,20 +952,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)
depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
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)
* 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
@@ -1130,23 +1253,72 @@ 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
@frappe.whitelist()

View File

@@ -36,7 +36,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

@@ -218,10 +218,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

@@ -818,12 +818,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 = [

View File

@@ -84,6 +84,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,43 +39,47 @@ class AssetRepair(AccountsController):
def before_submit(self):
self.check_repair_status()
if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.increase_asset_value()
if self.get("stock_consumption"):
self.check_for_stock_items_and_warehouse()
self.decrease_stock_quantity()
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
):
self.modify_depreciation_schedule()
self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
self.asset_doc.flags.ignore_validate_update_after_submit = True
self.asset_doc.prepare_depreciation_data()
self.asset_doc.save()
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"):
self.check_for_stock_items_and_warehouse()
self.decrease_stock_quantity()
if self.get("capitalize_repair_cost"):
self.make_gl_entries()
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
self.asset_doc.prepare_depreciation_data()
self.asset_doc.save()
def before_cancel(self):
self.asset_doc = frappe.get_doc("Asset", self.asset)
if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.decrease_asset_value()
if self.get("stock_consumption"):
self.increase_stock_quantity()
if self.get("capitalize_repair_cost"):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(cancel=True)
self.db_set("stock_entry", None)
if (
frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
and self.increase_in_asset_life
):
self.revert_depreciation_schedule_on_cancellation()
self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
self.asset_doc.flags.ignore_validate_update_after_submit = True
self.asset_doc.prepare_depreciation_data()
self.asset_doc.save()
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"):
self.increase_stock_quantity()
if self.get("capitalize_repair_cost"):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(cancel=True)
self.db_set("stock_entry", None)
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
self.asset_doc.prepare_depreciation_data()
self.asset_doc.save()
def after_delete(self):
frappe.get_doc("Asset", self.asset).set_status()

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

@@ -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.only_depreciable_assets == 1",
},
{
fieldname:"only_depreciable_assets",
label: __("Only depreciable assets"),
fieldtype: "Check"
},
{
fieldname:"only_existing_assets",
label: __("Only existing assets"),
fieldtype: "Check"
},
]

View File

@@ -45,8 +45,10 @@ 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_depreciable_assets"):
conditions["calculate_depreciation"] = filters.get("only_depreciable_assets")
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 +104,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.only_depreciable_assets:
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 +173,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 +311,7 @@ def get_columns(filters):
return [
{
"label": _("Asset Id"),
"label": _("Asset ID"),
"fieldtype": "Link",
"fieldname": "asset_id",
"options": "Asset",

View File

@@ -16,8 +16,10 @@
"transaction_settings_section",
"po_required",
"pr_required",
"over_order_allowance",
"column_break_12",
"maintain_same_rate",
"set_landed_cost_based_on_purchase_invoice_rate",
"allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice",
"disable_last_purchase_rate",
@@ -147,6 +149,21 @@
"fieldname": "show_pay_button",
"fieldtype": "Check",
"label": "Show Pay Button in Purchase Order Portal"
},
{
"default": "0",
"depends_on": "eval: !doc.maintain_same_rate",
"description": "Users can enable the checkbox If they want to adjust the incoming rate (set using purchase receipt) based on the purchase invoice rate.",
"fieldname": "set_landed_cost_based_on_purchase_invoice_rate",
"fieldtype": "Check",
"label": "Set Landed Cost Based on Purchase Invoice Rate"
},
{
"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 (%)"
}
],
"icon": "fa fa-cog",
@@ -154,7 +171,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-02-15 14:42:10.200679",
"modified": "2023-03-02 17:02:14.404622",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

View File

@@ -21,3 +21,10 @@ class BuyingSettings(Document):
self.get("supp_master_name") == "Naming Series",
hide_name_field=False,
)
def before_save(self):
self.check_maintain_same_rate()
def check_maintain_same_rate(self):
if self.maintain_same_rate:
self.set_landed_cost_based_on_purchase_invoice_rate = 0

View File

@@ -236,7 +236,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
this.make_purchase_invoice, __('Create'));
if(flt(doc.per_billed) < 100 && doc.status != "Delivered") {
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
this.frm.add_custom_button(
__('Payment'),
() => this.make_payment_entry(),
__('Create')
);
}
if(flt(doc.per_billed) < 100) {

View File

@@ -495,6 +495,7 @@
"allow_bulk_edit": 1,
"fieldname": "items",
"fieldtype": "Table",
"label": "Items",
"oldfieldname": "po_details",
"oldfieldtype": "Table",
"options": "Purchase Order Item",
@@ -1100,8 +1101,7 @@
{
"fieldname": "before_items_section",
"fieldtype": "Section Break",
"hide_border": 1,
"label": "Items"
"hide_border": 1
},
{
"fieldname": "items_col_break",
@@ -1271,7 +1271,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2023-01-28 18:59:16.322824",
"modified": "2023-04-14 16:42:29.448464",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

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
@@ -69,6 +72,7 @@ class PurchaseOrder(BuyingController):
self.validate_with_previous_doc()
self.validate_for_subcontracting()
self.validate_minimum_order_qty()
validate_against_blanket_order(self)
if self.is_old_subcontracting_flow:
self.validate_bom_for_subcontracting_items()

View File

@@ -113,7 +113,10 @@ class RequestforQuotation(BuyingController):
def get_link(self):
# RFQ link for supplier portal
return get_url("/app/request-for-quotation/" + self.name)
route = frappe.db.get_value(
"Portal Menu Item", {"reference_doctype": "Request for Quotation"}, ["route"]
)
return get_url("/app/{0}/".format(route) + self.name)
def update_supplier_part_no(self, supplier):
self.vendor = supplier

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

@@ -310,7 +310,6 @@
"fieldname": "items_section",
"fieldtype": "Section Break",
"hide_border": 1,
"label": "Items",
"oldfieldtype": "Section Break",
"options": "fa fa-shopping-cart"
},
@@ -318,6 +317,7 @@
"allow_bulk_edit": 1,
"fieldname": "items",
"fieldtype": "Table",
"label": "Items",
"oldfieldname": "po_details",
"oldfieldtype": "Table",
"options": "Supplier Quotation Item",
@@ -844,7 +844,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-12-12 18:35:39.740974",
"modified": "2023-04-14 16:43:41.714832",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",

View File

@@ -22,14 +22,14 @@ frappe.query_reports["Subcontracted Item To Be Received"] = {
fieldname:"from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
reqd: 1
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
default: frappe.datetime.get_today(),
reqd: 1
},
]

View File

@@ -22,14 +22,14 @@ frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = {
fieldname:"from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
reqd: 1
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
default: frappe.datetime.get_today(),
reqd: 1
},
]

View File

@@ -273,8 +273,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()
@@ -515,6 +515,7 @@ class AccountsController(TransactionBase):
parent_dict.update({"customer": parent_dict.get("party_name")})
self.pricing_rules = []
for item in self.get("items"):
if item.get("item_code"):
args = parent_dict.copy()
@@ -833,7 +834,9 @@ class AccountsController(TransactionBase):
def set_advances(self):
"""Returns list of advances against Account, Party, Reference"""
res = self.get_advance_entries()
res = self.get_advance_entries(
include_unallocated=not cint(self.get("only_include_allocated_payments"))
)
self.set("advances", [])
advance_allocated = 0
@@ -1232,7 +1235,7 @@ class AccountsController(TransactionBase):
)
)
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on):
from erpnext.controllers.status_updater import get_allowance_for
item_allowance = {}
@@ -1245,17 +1248,20 @@ class AccountsController(TransactionBase):
total_overbilled_amt = 0.0
reference_names = [d.get(item_ref_dn) for d in self.get("items") if d.get(item_ref_dn)]
reference_details = self.get_billing_reference_details(
reference_names, ref_dt + " Item", based_on
)
for item in self.get("items"):
if not item.get(item_ref_dn):
continue
ref_amt = flt(
frappe.db.get_value(ref_dt + " Item", item.get(item_ref_dn), based_on),
self.precision(based_on, item),
)
ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item))
if not ref_amt:
frappe.msgprint(
_("System will not check overbilling since amount for Item {0} in {1} is zero").format(
_("System will not check over billing since amount for Item {0} in {1} is zero").format(
item.item_code, ref_dt
),
title=_("Warning"),
@@ -1302,6 +1308,16 @@ class AccountsController(TransactionBase):
alert=True,
)
def get_billing_reference_details(self, reference_names, reference_doctype, based_on):
return frappe._dict(
frappe.get_all(
reference_doctype,
filters={"name": ("in", reference_names)},
fields=["name", based_on],
as_list=1,
)
)
def get_billed_amount_for_item(self, item, item_ref_dn, based_on):
"""
Returns Sum of Amount of
@@ -1591,6 +1607,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)
@@ -1636,19 +1653,20 @@ 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:
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":
@@ -1701,6 +1719,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

@@ -269,7 +269,10 @@ class BuyingController(SubcontractingController):
) / qty_in_stock_uom
else:
item.valuation_rate = (
item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
item.base_net_amount
+ item.item_tax_amount
+ flt(item.landed_cost_voucher_amount)
+ flt(item.get("rate_difference_with_purchase_invoice"))
) / qty_in_stock_uom
else:
item.valuation_rate = 0.0

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)
@@ -252,7 +252,6 @@ def get_already_returned_items(doc):
child.parent = par.name and par.docstatus = 1
and par.is_return = 1 and par.return_against = %s
group by item_code
for update
""".format(
column, doc.doctype, doc.doctype
),
@@ -306,7 +305,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=[
@@ -401,6 +400,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 in ["Purchase Receipt", "Subcontracting Receipt"]:
returned_qty_map = get_returned_qty_map_for_row(
source_parent.name, source_parent.supplier, source_doc.name, doctype
@@ -611,7 +620,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)
@@ -620,7 +629,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],
@@ -630,6 +639,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

@@ -139,7 +139,7 @@ class SellingController(StockController):
self.in_words = money_in_words(amount, self.currency)
def calculate_commission(self):
if not self.meta.get_field("commission_rate"):
if not self.meta.get_field("commission_rate") or self.docstatus.is_submitted():
return
self.round_floats_in(self, ("amount_eligible_for_commission", "commission_rate"))

View File

@@ -464,7 +464,7 @@ class StatusUpdater(Document):
ifnull((select
ifnull(sum(case when abs(%(target_ref_field)s) > abs(%(target_field)s) then abs(%(target_field)s) else abs(%(target_ref_field)s) end), 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

@@ -859,6 +859,8 @@ def is_reposting_pending():
def future_sle_exists(args, sl_entries=None):
key = (args.voucher_type, args.voucher_no)
if not hasattr(frappe.local, "future_sle"):
frappe.local.future_sle = {}
if validate_future_sle_not_exists(args, key, sl_entries):
return False
@@ -892,6 +894,9 @@ def future_sle_exists(args, sl_entries=None):
)
for d in data:
if key not in frappe.local.future_sle:
frappe.local.future_sle[key] = frappe._dict({})
frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row
return len(data)
@@ -903,6 +908,9 @@ def validate_future_sle_not_exists(args, key, sl_entries=None):
item_key = (args.get("item_code"), args.get("warehouse"))
if not sl_entries and hasattr(frappe.local, "future_sle"):
if key not in frappe.local.future_sle:
return False
if not frappe.local.future_sle.get(key) or (
item_key and item_key not in frappe.local.future_sle.get(key)
):
@@ -910,11 +918,8 @@ def validate_future_sle_not_exists(args, key, sl_entries=None):
def get_cached_data(args, key):
if not hasattr(frappe.local, "future_sle"):
frappe.local.future_sle = {}
if key not in frappe.local.future_sle:
frappe.local.future_sle[key] = frappe._dict({})
return False
if args.get("item_code"):
item_key = (args.get("item_code"), args.get("warehouse"))

View File

@@ -455,7 +455,7 @@ class SubcontractingController(StockController):
"allow_zero_valuation": 1,
}
)
rm_obj.rate = get_incoming_rate(args)
rm_obj.rate = bom_item.rate if self.backflush_based_on == "BOM" else get_incoming_rate(args)
if self.doctype == self.subcontract_data.order_doctype:
rm_obj.required_qty = qty

View File

@@ -24,11 +24,19 @@ class calculate_taxes_and_totals(object):
def __init__(self, doc: Document):
self.doc = doc
frappe.flags.round_off_applicable_accounts = []
self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
self.calculate()
def filter_rows(self):
"""Exclude rows, that do not fulfill the filter criteria, from totals computation."""
items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items")))
return items
def calculate(self):
if not len(self.doc.get("items")):
if not len(self._items):
return
self.discount_amount_applied = False
@@ -70,7 +78,7 @@ class calculate_taxes_and_totals(object):
if hasattr(self.doc, "tax_withholding_net_total"):
sum_net_amount = 0
sum_base_net_amount = 0
for item in self.doc.get("items"):
for item in self._items:
if hasattr(item, "apply_tds") and item.apply_tds:
sum_net_amount += item.net_amount
sum_base_net_amount += item.base_net_amount
@@ -79,7 +87,7 @@ class calculate_taxes_and_totals(object):
self.doc.base_tax_withholding_net_total = sum_base_net_amount
def validate_item_tax_template(self):
for item in self.doc.get("items"):
for item in self._items:
if item.item_code and item.get("item_tax_template"):
item_doc = frappe.get_cached_doc("Item", item.item_code)
args = {
@@ -137,7 +145,7 @@ class calculate_taxes_and_totals(object):
return
if not self.discount_amount_applied:
for item in self.doc.get("items"):
for item in self._items:
self.doc.round_floats_in(item)
if item.discount_percentage == 100:
@@ -236,7 +244,7 @@ class calculate_taxes_and_totals(object):
if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")):
return
for item in self.doc.get("items"):
for item in self._items:
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
cumulated_tax_fraction = 0
total_inclusive_tax_amount_per_qty = 0
@@ -317,7 +325,7 @@ class calculate_taxes_and_totals(object):
self.doc.total
) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
for item in self.doc.get("items"):
for item in self._items:
self.doc.total += item.amount
self.doc.total_qty += item.qty
self.doc.base_total += item.base_amount
@@ -354,7 +362,7 @@ class calculate_taxes_and_totals(object):
]
)
for n, item in enumerate(self.doc.get("items")):
for n, item in enumerate(self._items):
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
for i, tax in enumerate(self.doc.get("taxes")):
# tax_amount represents the amount of tax for the current step
@@ -363,7 +371,7 @@ class calculate_taxes_and_totals(object):
# Adjust divisional loss to the last item
if tax.charge_type == "Actual":
actual_tax_dict[tax.idx] -= current_tax_amount
if n == len(self.doc.get("items")) - 1:
if n == len(self._items) - 1:
current_tax_amount += actual_tax_dict[tax.idx]
# accumulate tax amount into tax.tax_amount
@@ -391,7 +399,7 @@ class calculate_taxes_and_totals(object):
)
# set precision in the last item iteration
if n == len(self.doc.get("items")) - 1:
if n == len(self._items) - 1:
self.round_off_totals(tax)
self._set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"])
@@ -570,7 +578,7 @@ class calculate_taxes_and_totals(object):
def calculate_total_net_weight(self):
if self.doc.meta.get_field("total_net_weight"):
self.doc.total_net_weight = 0.0
for d in self.doc.items:
for d in self._items:
if d.total_weight:
self.doc.total_net_weight += d.total_weight
@@ -630,7 +638,7 @@ class calculate_taxes_and_totals(object):
if total_for_discount_amount:
# calculate item amount after Discount Amount
for i, item in enumerate(self.doc.get("items")):
for i, item in enumerate(self._items):
distributed_amount = (
flt(self.doc.discount_amount) * item.net_amount / total_for_discount_amount
)
@@ -643,7 +651,7 @@ class calculate_taxes_and_totals(object):
self.doc.apply_discount_on == "Net Total"
or not taxes
or total_for_discount_amount == self.doc.net_total
) and i == len(self.doc.get("items")) - 1:
) and i == len(self._items) - 1:
discount_amount_loss = flt(
self.doc.net_total - net_total - self.doc.discount_amount, self.doc.precision("net_total")
)

View File

@@ -76,12 +76,9 @@ def get_transaction_list(
ignore_permissions = False
if not filters:
filters = []
filters = {}
if doctype in ["Supplier Quotation", "Purchase Invoice"]:
filters.append((doctype, "docstatus", "<", 2))
else:
filters.append((doctype, "docstatus", "=", 1))
filters["docstatus"] = ["<", "2"] if doctype in ["Supplier Quotation", "Purchase Invoice"] else 1
if (user != "Guest" and is_website_user()) or doctype == "Request for Quotation":
parties_doctype = (
@@ -92,12 +89,12 @@ def get_transaction_list(
if customers:
if doctype == "Quotation":
filters.append(("quotation_to", "=", "Customer"))
filters.append(("party_name", "in", customers))
filters["quotation_to"] = "Customer"
filters["party_name"] = ["in", customers]
else:
filters.append(("customer", "in", customers))
filters["customer"] = ["in", customers]
elif suppliers:
filters.append(("supplier", "in", suppliers))
filters["supplier"] = ["in", suppliers]
elif not custom:
return []
@@ -110,7 +107,7 @@ def get_transaction_list(
if not customers and not suppliers and custom:
ignore_permissions = False
filters = []
filters = {}
transactions = get_list_for_transactions(
doctype,

View File

@@ -19,10 +19,6 @@ frappe.ui.form.on("Opportunity", {
}
}
});
if (frm.doc.opportunity_from && frm.doc.party_name){
frm.trigger('set_contact_link');
}
},
validate: function(frm) {
@@ -130,6 +126,10 @@ frappe.ui.form.on("Opportunity", {
} else {
frappe.contacts.clear_address_and_contact(frm);
}
if (frm.doc.opportunity_from && frm.doc.party_name) {
frm.trigger('set_contact_link');
}
},
set_contact_link: function(frm) {
@@ -137,6 +137,8 @@ frappe.ui.form.on("Opportunity", {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Customer'}
} else if(frm.doc.opportunity_from == "Lead" && frm.doc.party_name) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Lead'}
} else if (frm.doc.opportunity_from == "Prospect" && frm.doc.party_name) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Prospect'}
}
},

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

@@ -82,7 +82,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

@@ -199,8 +199,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

@@ -315,6 +315,7 @@ class WebsiteItem(WebsiteGenerator):
self.item_code, skip_quotation_creation=True
)
@frappe.whitelist()
def copy_specification_from_item_group(self):
self.set("website_specifications", [])
if self.item_group:

View File

@@ -1,5 +1,5 @@
import frappe
from frappe.utils import cint
from frappe.utils import cint, flt
from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
get_shopping_cart_settings,
@@ -166,6 +166,27 @@ def get_next_attribute_and_values(item_code, selected_attributes):
else:
product_info = None
product_id = ""
website_warehouse = ""
if exact_match or filtered_items:
if exact_match and len(exact_match) == 1:
product_id = exact_match[0]
elif filtered_items_count == 1:
product_id = list(filtered_items)[0]
if product_id:
website_warehouse = frappe.get_cached_value(
"Website Item", {"item_code": product_id}, "website_warehouse"
)
available_qty = 0.0
if website_warehouse:
available_qty = flt(
frappe.db.get_value(
"Bin", {"item_code": product_id, "warehouse": website_warehouse}, "actual_qty"
)
)
return {
"next_attribute": next_attribute,
"valid_options_for_attributes": valid_options_for_attributes,
@@ -173,6 +194,7 @@ def get_next_attribute_and_values(item_code, selected_attributes):
"filtered_items": filtered_items if filtered_items_count < 10 else [],
"exact_match": exact_match,
"product_info": product_info,
"available_qty": available_qty,
}

View File

@@ -12,7 +12,7 @@ class PlaidConnector:
def __init__(self, access_token=None):
self.access_token = access_token
self.settings = frappe.get_single("Plaid Settings")
self.products = ["auth", "transactions"]
self.products = ["transactions"]
self.client_name = frappe.local.site
self.client = plaid.Client(
client_id=self.settings.plaid_client_id,

View File

@@ -47,7 +47,7 @@ erpnext.integrations.plaidLink = class plaidLink {
}
async init_config() {
this.product = ["auth", "transactions"];
this.product = ["transactions"];
this.plaid_env = this.frm.doc.plaid_env;
this.client_name = frappe.boot.sitename;
this.token = await this.get_link_token();

View File

@@ -70,7 +70,8 @@ def add_bank_accounts(response, bank, company):
except TypeError:
pass
bank = json.loads(bank)
if isinstance(bank, str):
bank = json.loads(bank)
result = []
default_gl_account = get_default_bank_cash_account(company, "Bank")
@@ -177,16 +178,15 @@ def sync_transactions(bank, bank_account):
)
result = []
for transaction in reversed(transactions):
result += new_bank_transaction(transaction)
if transactions:
for transaction in reversed(transactions):
result += new_bank_transaction(transaction)
if result:
last_transaction_date = frappe.db.get_value("Bank Transaction", result.pop(), "date")
frappe.logger().info(
"Plaid added {} new Bank Transactions from '{}' between {} and {}".format(
len(result), bank_account, start_date, end_date
)
f"Plaid added {len(result)} new Bank Transactions from '{bank_account}' between {start_date} and {end_date}"
)
frappe.db.set_value(
@@ -220,7 +220,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
if e.code == "ITEM_LOGIN_REQUIRED":
msg = _("There was an error syncing transactions.") + " "
msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
frappe.log_error(message=msg, title=_("Plaid Link Refresh Required"))
return transactions
@@ -230,19 +230,20 @@ def new_bank_transaction(transaction):
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
if float(transaction["amount"]) >= 0:
debit = 0
credit = float(transaction["amount"])
amount = float(transaction["amount"])
if amount >= 0.0:
deposit = 0.0
withdrawal = amount
else:
debit = abs(float(transaction["amount"]))
credit = 0
deposit = abs(amount)
withdrawal = 0.0
status = "Pending" if transaction["pending"] == "True" else "Settled"
tags = []
try:
tags += transaction["category"]
tags += ["Plaid Cat. {}".format(transaction["category_id"])]
tags += [f'Plaid Cat. {transaction["category_id"]}']
except KeyError:
pass
@@ -254,11 +255,18 @@ def new_bank_transaction(transaction):
"date": getdate(transaction["date"]),
"status": status,
"bank_account": bank_account,
"deposit": debit,
"withdrawal": credit,
"deposit": deposit,
"withdrawal": withdrawal,
"currency": transaction["iso_currency_code"],
"transaction_id": transaction["transaction_id"],
"reference_number": transaction["payment_meta"]["reference_number"],
"transaction_type": (
transaction["transaction_code"] or transaction["payment_meta"]["payment_method"]
),
"reference_number": (
transaction["check_number"]
or transaction["payment_meta"]["reference_number"]
or transaction["name"]
),
"description": transaction["name"],
}
)
@@ -271,7 +279,7 @@ def new_bank_transaction(transaction):
result.append(new_transaction.name)
except Exception:
frappe.throw(title=_("Bank transaction creation error"))
frappe.throw(_("Bank transaction creation error"))
return result
@@ -300,3 +308,26 @@ def enqueue_synchronization():
def get_link_token_for_update(access_token):
plaid = PlaidConnector(access_token)
return plaid.get_link_token(update_mode=True)
def get_company(bank_account_name):
from frappe.defaults import get_user_default
company_names = frappe.db.get_all("Company", pluck="name")
if len(company_names) == 1:
return company_names[0]
if frappe.db.exists("Bank Account", bank_account_name):
return frappe.db.get_value("Bank Account", bank_account_name, "company")
company_default = get_user_default("Company")
if company_default:
return company_default
frappe.throw(_("Could not detect the Company for updating Bank Accounts"))
@frappe.whitelist()
def update_bank_account_ids(response):
data = json.loads(response)
institution_name = data["institution"]["name"]
bank = frappe.get_doc("Bank", institution_name).as_dict()
bank_account_name = f"{data['account']['name']} - {institution_name}"
return add_bank_accounts(response, bank, get_company(bank_account_name))

View File

@@ -125,6 +125,8 @@ class TestPlaidSettings(unittest.TestCase):
"unofficial_currency_code": None,
"name": "INTRST PYMNT",
"transaction_type": "place",
"transaction_code": "direct debit",
"check_number": "3456789",
"amount": -4.22,
"location": {
"city": None,

View File

@@ -276,7 +276,7 @@ has_website_permission = {
before_tests = "erpnext.setup.utils.before_tests"
standard_queries = {
"Customer": "erpnext.selling.doctype.customer.customer.get_customer_list",
"Customer": "erpnext.controllers.queries.customer_query",
}
doc_events = {
@@ -368,7 +368,7 @@ auto_cancel_exempted_doctypes = [
scheduler_events = {
"cron": {
"0/5 * * * *": [
"0/15 * * * *": [
"erpnext.manufacturing.doctype.bom_update_log.bom_update_log.resume_bom_cost_update_jobs",
],
"0/30 * * * *": [

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");
},

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