Compare commits

..

200 Commits

Author SHA1 Message Date
Frappe PR Bot
c15cff05b0 chore(release): Bumped to Version 13.36.3
## [13.36.3](https://github.com/frappe/erpnext/compare/v13.36.2...v13.36.3) (2022-08-09)

### Bug Fixes

* (india) HSN wise report ([08c69c7](08c69c7a76))
* add asset repair to accounting dimension list ([809d5ca](809d5caf80))
* consider precision while validating advance amount against sanctioned amount ([63cd434](63cd4349a6))
* **ecommerce:** remove query to non-existing field (backport [#31771](https://github.com/frappe/erpnext/issues/31771)) ([#31774](https://github.com/frappe/erpnext/issues/31774)) ([0e46b33](0e46b33ee3))
* f-string and where clause ([42b3959](42b395916d))
* getting error to show sales invoice group or print rep… (backport [#31756](https://github.com/frappe/erpnext/issues/31756)) ([#31768](https://github.com/frappe/erpnext/issues/31768)) ([473a43b](473a43b6b1))
* incorrect incoming rate set for inter transfer purchase receipt ([4e8b39a](4e8b39ab3f))
* incorrect tax calculation in case of reduced payment days ([80981d0](80981d025f))
* intercompany SO throws exception ([a17acfd](a17acfdaa2))
* minor changed link ([e9e53a7](e9e53a74c9))
* Payment Entry button is visible even when claim is fully paid ([d820757](d820757359))
* pending principal- amount ([16c94d2](16c94d292c))
* **pos:** validate product bundles while submitting pos invoice (backport [#31615](https://github.com/frappe/erpnext/issues/31615)) ([#31657](https://github.com/frappe/erpnext/issues/31657)) ([b145fe3](b145fe3b3e))
* specify allowed doctype in queries ([#31765](https://github.com/frappe/erpnext/issues/31765)) ([0dbfb15](0dbfb1589e))
* statistical component showing up in salary slip ([#31746](https://github.com/frappe/erpnext/issues/31746)) ([26aef4f](26aef4fb1c))
* sum stock_value and group by warehouse ([18d93f8](18d93f8398))
* taxable_value and gst_account_heads ([6c574fb](6c574fbf33))
* update To Date in Employee Work History ([#31811](https://github.com/frappe/erpnext/issues/31811)) ([0057c10](0057c10a7d))
2022-08-09 14:09:11 +00:00
Deepesh Garg
b80526f226 Merge pull request #31812 from frappe/version-13-hotfix
chore: weekly version 13 release
2022-08-09 19:37:13 +05:30
Deepesh Garg
0569f8a857 Merge pull request #31736 from maharshivpatel/fix-india-hsn-report
fix: (india) HSN wise report
2022-08-09 18:58:25 +05:30
Rucha Mahabal
63715bf229 Merge pull request #31813 from ruchamahabal/fix-claim 2022-08-09 18:52:22 +05:30
Rucha Mahabal
63cd4349a6 fix: consider precision while validating advance amount against sanctioned amount 2022-08-09 18:28:19 +05:30
Maharshi Patel
42b395916d fix: f-string and where clause
Used f-string formatting and added conditions to WHERE clause
2022-08-09 18:22:05 +05:30
Rucha Mahabal
0057c10a7d fix: update To Date in Employee Work History (#31811) 2022-08-09 18:19:09 +05:30
Deepesh Garg
3c8c5d01fb Merge pull request #31797 from frappe/mergify/bp/version-13-hotfix/pr-31779
Bug add accouting dimension in asset repair (backport #31779)
2022-08-09 18:18:42 +05:30
Rucha Mahabal
d820757359 fix: Payment Entry button is visible even when claim is fully paid 2022-08-09 18:15:41 +05:30
mergify[bot]
14e59c86aa Tds report (backport #31801) (#31808)
* fix: TDS Computation Summary Report not loading, too many values to unpack
2022-08-09 18:11:13 +05:30
Deepesh Garg
9e16f4e412 chore: resolve conflicts 2022-08-09 17:55:02 +05:30
rohitwaghchaure
6d269a4d89 Merge pull request #31805 from frappe/mergify/bp/version-13-hotfix/pr-31804
fix: incorrect incoming rate set for inter transfer purchase receipt (backport #31804)
2022-08-09 16:26:34 +05:30
Rohit Waghchaure
4e8b39ab3f fix: incorrect incoming rate set for inter transfer purchase receipt
(cherry picked from commit ddd24ea8c8)
2022-08-09 10:31:14 +00:00
Rucha Mahabal
9925eb9982 Merge pull request #31802 from ruchamahabal/fix-salary-slip-tds-v13 2022-08-09 15:33:54 +05:30
Rucha Mahabal
301d199ece test: default amount in slip 2022-08-09 14:57:10 +05:30
Maharshi Patel
6c574fbf33 fix: taxable_value and gst_account_heads
used taxable_value instead of base_net_amount and only appended required GST accounts
2022-08-09 14:39:13 +05:30
Rucha Mahabal
80981d025f fix: incorrect tax calculation in case of reduced payment days 2022-08-09 12:40:06 +05:30
ruthra kumar
a420d0242c chore: patch for creating existing dimensions in asset repair
(cherry picked from commit 80f508c4b1)

# Conflicts:
#	erpnext/patches.txt
2022-08-08 11:09:55 +00:00
ruthra kumar
809d5caf80 fix: add asset repair to accounting dimension list
(cherry picked from commit 452584c4bd)
2022-08-08 11:09:53 +00:00
Deepesh Garg
6f442d14cf Merge pull request #31795 from frappe/mergify/bp/version-13-hotfix/pr-31777
fix: intercompany SO created from Purchase Order throws exception (backport #31777)
2022-08-08 16:37:39 +05:30
ruthra kumar
a17acfdaa2 fix: intercompany SO throws exception
(cherry picked from commit af0a353b79)
2022-08-08 10:37:25 +00:00
Deepesh Garg
3673dea03b Merge pull request #31783 from frappe/mergify/bp/version-13-hotfix/pr-31780
Fix: Loan pending principal amount  (backport #31780)
2022-08-08 14:48:06 +05:30
Deepesh Garg
fb3725752f chore: resolve conflicts 2022-08-08 11:53:49 +05:30
Abhinav Raut
16c94d292c fix: pending principal- amount
(cherry picked from commit a272d73dd9)

# Conflicts:
#	erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
2022-08-05 10:35:05 +00:00
mergify[bot]
0e46b33ee3 fix(ecommerce): remove query to non-existing field (backport #31771) (#31774)
fix(ecommerce): remove query to non-existing field (#31771)

(cherry picked from commit 17b9bfd249)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-08-03 17:08:46 +05:30
mergify[bot]
df716fbd0c ci: fix automated release regex (backport #31770) (#31772)
ci: fix automated release regex (#31770)

[skip ci]

(cherry picked from commit 2defb89962)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-08-03 16:37:39 +05:30
mergify[bot]
0dbfb1589e fix: specify allowed doctype in queries (#31765)
Co-authored-by: Sagar Vora <sagar@resilient.tech>
2022-08-03 12:03:53 +05:30
mergify[bot]
473a43b6b1 fix: getting error to show sales invoice group or print rep… (backport #31756) (#31768)
fix: getting error to show sales invoice group or print rep… (#31756)

fix: formatter getting error to show sales invoice group or print report.

1 - When I view the Gross Profit report in Sales Invoice mode, the table is all broken.
Error on browser console:
TypeError: Cannot read properties of undefined (reading 'indent')

2 - When I try to print, no matter the Group (Sales Invoice, Item Code, Item Group...) nothing happens. in browser log console I have the following error:
TypeError: Cannot read properties of undefined (reading 'content')

i fixed both errors and all working perfectly.

(cherry picked from commit ea88451875)

Co-authored-by: HarryPaulo <paulo_fabris@hotmail.com>
2022-08-03 11:23:10 +05:30
rohitwaghchaure
1a3b3b96c2 Merge pull request #31751 from frappe/mergify/bp/version-13-hotfix/pr-31750
fix: minor URL link (backport #31750)
2022-08-01 14:34:12 +05:30
Rohit Waghchaure
e9e53a74c9 fix: minor changed link
(cherry picked from commit 0e7c4314b4)
2022-08-01 09:02:07 +00:00
Rucha Mahabal
26aef4fb1c fix: statistical component showing up in salary slip (#31746)
* fix: statistical component showing up in salary slip

* fix(test): payment days effect on timesheet salary slio
2022-08-01 12:54:25 +05:30
mergify[bot]
b145fe3b3e fix(pos): validate product bundles while submitting pos invoice (backport #31615) (#31657) 2022-07-31 19:12:14 +05:30
Marica
d83c869d73 Merge pull request #31717 from frappe/mergify/bp/version-13-hotfix/pr-31601
perf: reduce db calls for warehouse wise stock value chart (backport #31601)
2022-07-31 18:26:27 +05:30
Maharshi Patel
08c69c7a76 fix: (india) HSN wise report
Problem:
The previous approach for calculating tax_rate was incorrect.
```
['tax_rate'] * ['number of unique rows']
```
Joining `tabSales Taxes and Charges` was adding unnecessary rows & complexity.

Solution:
Instead of trying to get tax_rate from the main query itself, I used the get_tax_accounts's data to calculate the correct tax_rate.

Todo:
Union Territory
2022-07-29 14:22:31 +05:30
Frappe PR Bot
84e6cead56 chore(release): Bumped to Version 13.36.2
## [13.36.2](https://github.com/frappe/erpnext/compare/v13.36.1...v13.36.2) (2022-07-28)

### Bug Fixes

* (india) (e-invoice) margin & internal company transfer ([b97d30a](b97d30aad0))
* (india) add overseas in HSN wise report ([1d69ce1](1d69ce1932))
* Allow allocating advance amount against Expense Claim taxes ([42f3592](42f3592e95))
* assign duplicate_items_msg outside conditional ([#31639](https://github.com/frappe/erpnext/issues/31639)) ([8e23c6a](8e23c6ad69))
* correct Brazilian portuguese translations (backport [#31498](https://github.com/frappe/erpnext/issues/31498)) ([#31660](https://github.com/frappe/erpnext/issues/31660)) ([794fd08](794fd0819f))
* discount and test added ([a843e78](a843e784e6))
* display customer name on picking list ([dbf245c](dbf245c687))
* do not update component amount for timesheet components ([#31696](https://github.com/frappe/erpnext/issues/31696)) ([bc7cfe6](bc7cfe6919))
* enable tax withholding checkbox in PI with supplier_tds ([bc74942](bc7494278d))
* ensure defaults removed in bad frappe patch get set again (backport [#31659](https://github.com/frappe/erpnext/issues/31659)) ([#31661](https://github.com/frappe/erpnext/issues/31661)) ([763787b](763787b0a5))
* hero image not loading in portal homepage ([#31699](https://github.com/frappe/erpnext/issues/31699)) ([8a6432e](8a6432ec3f))
* **india:** e-way bill json for unregistered gst category ([01d6df4](01d6df45d0))
* make customer_name field read only. ([2381b81](2381b81aac))
* manually generated salary slips overwritten by structure amount ([#31711](https://github.com/frappe/erpnext/issues/31711)) ([32c1bb6](32c1bb61de))
* Map `Item` image to `Website Item` website_image only if published via UI (v13) ([9b1544a](9b1544aa14))
* payment entry to student ([#31708](https://github.com/frappe/erpnext/issues/31708)) ([4f02375](4f023757de))
* Reload loan Table in Salary Slip when change Employee ([#31525](https://github.com/frappe/erpnext/issues/31525)) ([a95c011](a95c011a93))
* rounding errors while closing pos (backport [#31654](https://github.com/frappe/erpnext/issues/31654)) ([#31658](https://github.com/frappe/erpnext/issues/31658)) ([529a47b](529a47bc88))
* Route condition set with proper filter ([#31556](https://github.com/frappe/erpnext/issues/31556)) ([5cdc267](5cdc267aee))
* The Fee details are not fetched in Program Enrollment ([#31153](https://github.com/frappe/erpnext/issues/31153)) ([0602848](0602848caa))
* update fr translations ([#31687](https://github.com/frappe/erpnext/issues/31687)) ([89348c1](89348c1bb4))
* update fr translations (backport [#31526](https://github.com/frappe/erpnext/issues/31526)) ([#31666](https://github.com/frappe/erpnext/issues/31666)) ([95e1021](95e1021caf))
* update SO's percentage billed on credit note ([2f2d3de](2f2d3de306))
* use current pos profile on sales return ([c442b4a](c442b4aef1))
2022-07-28 12:13:57 +00:00
Deepesh Garg
590e48fa1d Merge pull request #31725 from frappe/version-13-hotfix
chore: weekly version-13 release
2022-07-28 17:42:15 +05:30
Deepesh Garg
25da4d28ff Merge pull request #31727 from frappe/mergify/bp/version-13-hotfix/pr-31656
fix: use current pos profile on sales return (backport #31656)
2022-07-28 16:32:36 +05:30
Deepesh Garg
f57d2fadbc Merge pull request #31728 from frappe/mergify/bp/version-13-hotfix/pr-31676
fix: enable tax withholding checkbox in PI with supplier_tds (backport #31676)
2022-07-28 16:32:17 +05:30
Deepesh Garg
9e7edd677a Merge pull request #31726 from frappe/mergify/bp/version-13-hotfix/pr-31576
fix: credite note for returned delivery note updates SO's billed percentage (backport #31576)
2022-07-28 16:32:05 +05:30
ruthra kumar
bc7494278d fix: enable tax withholding checkbox in PI with supplier_tds
(cherry picked from commit b461724416)
2022-07-28 10:42:58 +00:00
ruthra kumar
c442b4aef1 fix: use current pos profile on sales return
(cherry picked from commit 5b85af5f1a)
2022-07-28 10:36:23 +00:00
ruthra kumar
ef9b25cf08 test: SO percentage billed when cr_note made against delivery return
(cherry picked from commit 243f66fcd3)
2022-07-28 10:21:19 +00:00
ruthra kumar
2f2d3de306 fix: update SO's percentage billed on credit note
Credit Note created from Sales Return will update precentage billed in
Sales Order accordingly

(cherry picked from commit 04c1019242)
2022-07-28 10:21:18 +00:00
Rucha Mahabal
13639682ce Merge pull request #31724 from ruchamahabal/expense-claim-adv 2022-07-28 14:51:15 +05:30
Rucha Mahabal
c01adae8a5 test: advance amount allocation against expense claim with taxes 2022-07-28 14:27:09 +05:30
Deepesh Garg
8ae691d9dc Merge branch 'version-13' into version-13-hotfix 2022-07-28 13:59:25 +05:30
Alaa Alsalehi
a95c011a93 fix: Reload loan Table in Salary Slip when change Employee (#31525)
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
Co-authored-by: bahaaabed <bahaa.9999.sh@gmail.com>
Co-authored-by: newhr1 <104132586+newhr1@users.noreply.github.com>
2022-07-28 13:22:07 +05:30
Rucha Mahabal
42f3592e95 fix: Allow allocating advance amount against Expense Claim taxes 2022-07-28 13:21:29 +05:30
Syed Mujeer Hashmi
0602848caa fix: The Fee details are not fetched in Program Enrollment (#31153) 2022-07-28 12:30:39 +05:30
Rucha Mahabal
9985d28571 chore: Add HR & Payroll deprecation warning (#31720) 2022-07-28 11:43:49 +05:30
Marica
31824c2280 Merge pull request #31716 from frappe/mergify/bp/version-13-hotfix/pr-31687
fix: update fr translations (backport #31687)
2022-07-27 18:01:30 +05:30
Devin Slauenwhite
18d93f8398 fix: sum stock_value and group by warehouse
(cherry picked from commit 1e20358c28)
2022-07-27 12:18:39 +00:00
Devin Slauenwhite
c9443123f9 chore: remove unused import
(cherry picked from commit bc3023318e)
2022-07-27 12:18:38 +00:00
Devin Slauenwhite
0fdec8fac8 pref: reduce count of db calls from n to 2
(cherry picked from commit 73ade04dcf)
2022-07-27 12:18:38 +00:00
marination
6fbb06a878 chore: Merge Conflict 2022-07-27 17:30:46 +05:30
HENRY Florian
89348c1bb4 fix: update fr translations (#31687)
* fix: update fr translations

* fix: update fr translation

* fix: update fr translation

* chore: Replace apostrophe encoding by symbol

Co-authored-by: marination <maricadsouza221197@gmail.com>
(cherry picked from commit cc1f837685)

# Conflicts:
#	erpnext/translations/fr.csv
2022-07-27 11:52:03 +00:00
Marica
598dbc93ac Merge pull request #31669 from frappe/mergify/bp/version-13-hotfix/pr-31579
fix: display customer name on picking list (backport #31579)
2022-07-27 16:45:07 +05:30
Marica
51a4ef3069 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-31579 2022-07-27 16:23:22 +05:30
Marica
49fb177f86 chore: Merge Conflicts 2022-07-27 16:22:17 +05:30
Marica
b0aae8a297 Merge pull request #31713 from frappe/mergify/bp/version-13-hotfix/pr-31469
chore: Make `image` field obsolete in Website Item (redundant) (backport #31469)
2022-07-27 16:12:32 +05:30
Marica
e310347ca4 chore: Merge Conflicts 2022-07-27 15:51:01 +05:30
marination
9b1544aa14 fix: Map Item image to Website Item website_image only if published via UI (v13)
- For v12 Items, `website_image` should be mapped from `Item` to `Website Item`

(cherry picked from commit af38baeb3b)

# Conflicts:
#	erpnext/e_commerce/doctype/website_item/website_item.py
2022-07-27 10:14:20 +00:00
marination
a8da5f4566 chore: Remove image use in website item list
(cherry picked from commit 7a6ee8cf2d)
2022-07-27 10:14:18 +00:00
marination
0e469f6d95 chore: Remove image from ProductQuery fields
(cherry picked from commit eec07833f4)
2022-07-27 10:14:18 +00:00
marination
b393c230bd chore: Make image field obsolete in Website Item (redundant)
- Delete Image field and set `website_image` as form's image field for uploads
- Remove instances of `image` field access via Website Item
- Item -> Web Item via Desk: Map Item's `image` to Web Item's `website_image`
- Item -> Web Item via patch: `website_image` will be mapped with thumbnail
- Remove magic that auto-sets `website_image` from `image` in Website Item

(cherry picked from commit 9541354ec7)

# Conflicts:
#	erpnext/e_commerce/doctype/website_item/website_item.py
2022-07-27 10:14:16 +00:00
Rucha Mahabal
32c1bb61de fix: manually generated salary slips overwritten by structure amount (#31711) 2022-07-27 12:44:26 +05:30
MohsinAli
4f023757de fix: payment entry to student (#31708)
Payment entry to student via Payment References(From Journal Entry)
2022-07-27 11:50:15 +05:30
Deepesh Garg
d2d25e17bb Merge pull request #31698 from maharshivpatel/fix-einvoice-margin-internal-transfer
fix: (india) (e-invoice) margin & internal company transfer
2022-07-26 21:59:44 +05:30
Maharshi Patel
a843e784e6 fix: discount and test added
only report net rate if discount is less than 0 and added test.
2022-07-26 17:40:59 +05:30
Rucha Mahabal
8a6432ec3f fix: hero image not loading in portal homepage (#31699) 2022-07-26 13:43:36 +05:30
Maharshi Patel
b97d30aad0 fix: (india) (e-invoice) margin & internal company transfer
When the item price is more than the price list rate ( margin added ) discount value becomes negative. The previous attempt to solve this was to convert discount to absolute value. However, that gives incorrect unit price and discount value. To solve this, I have made changes to report net rates in cases where the margin is added or is an internal company transfer.
2022-07-26 12:57:47 +05:30
Rucha Mahabal
bc7cfe6919 fix: do not update component amount for timesheet components (#31696)
* fix: do not update component amount for timesheet components

* fix: warn the user about overwriting timesheet component amount
2022-07-26 12:35:50 +05:30
Deepesh Garg
85802b0f97 Merge pull request #31602 from rtdany10/patch-19
fix(india): e-way bill json for unregistered gst category
2022-07-22 13:11:19 +05:30
Deepesh Garg
dc67d39ce6 Merge pull request #31665 from maharshivpatel/fix-add-overseas-hsn-report
fix: (india) add entries from overseas invoices in HSN wise report
2022-07-22 13:06:30 +05:30
mergify[bot]
529a47bc88 fix: rounding errors while closing pos (backport #31654) (#31658) 2022-07-22 11:17:59 +05:30
Devin Slauenwhite
2381b81aac fix: make customer_name field read only.
(cherry picked from commit 7083b3148b)

# Conflicts:
#	erpnext/stock/doctype/pick_list/pick_list.json
2022-07-21 13:08:41 +00:00
Devin Slauenwhite
dbf245c687 fix: display customer name on picking list
(cherry picked from commit 0a633a212d)

# Conflicts:
#	erpnext/stock/doctype/pick_list/pick_list.json
2022-07-21 13:08:40 +00:00
Nihantra C. Patel
5cdc267aee fix: Route condition set with proper filter (#31556)
* fix: Redirect to report with proper filter

* Update member.js

* fix: Route condition set with proper filter

* fix: Route condition set with proper filter

* fix: Route condition set with proper filter

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2022-07-21 18:29:06 +05:30
mergify[bot]
95e1021caf fix: update fr translations (backport #31526) (#31666)
fix: update fr translations (#31526)

* fix: update fr translations

* fix: update fr translations

* fix: update fr translations

* fix: update fr translations

* chore: Update french translation

Co-authored-by: Marica <maricadsouza221197@gmail.com>
(cherry picked from commit 3ba0d6cc5c)

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
2022-07-21 18:11:32 +05:30
mergify[bot]
7d50f8798a Update de.csv (backport #31596) (#31667)
Update de.csv (#31596)

IN,IM to IN,EIN

Co-authored-by: Marica <maricadsouza221197@gmail.com>
(cherry picked from commit 8629d01dd5)

Co-authored-by: billy995 <48571007+billy995@users.noreply.github.com>
2022-07-21 18:10:46 +05:30
Marica
dd751c6e92 Merge pull request #31664 from frappe/mergify/bp/version-13-hotfix/pr-31544
FIX: quality inspection quick creation from purchase receipt: pre-fill sample size if available (backport #31544)
2022-07-21 14:13:48 +05:30
Frappe PR Bot
69087b8790 chore(release): Bumped to Version 13.36.1
## [13.36.1](https://github.com/frappe/erpnext/compare/v13.36.0...v13.36.1) (2022-07-21)

### Bug Fixes

* ensure defaults removed in bad frappe patch get set again (backport [#31659](https://github.com/frappe/erpnext/issues/31659)) ([2ae3bd9](2ae3bd9fed))
2022-07-21 08:26:20 +00:00
mergify[bot]
763787b0a5 fix: ensure defaults removed in bad frappe patch get set again (backport #31659) (#31661)
fix: ensure defaults removed in bad frappe patch get set again (#31659)

(cherry picked from commit bf2833b8ee)

Co-authored-by: Sagar Vora <sagar@resilient.tech>
2022-07-21 13:54:55 +05:30
mergify[bot]
2ae3bd9fed fix: ensure defaults removed in bad frappe patch get set again (backport #31659)
fix: ensure defaults removed in bad frappe patch get set again (#31659)

(cherry picked from commit bf2833b8ee)
(cherry picked from commit 9071018e4e)

Co-authored-by: Sagar Vora <sagar@resilient.tech>
2022-07-21 13:54:40 +05:30
Marc de Lima Lucio
1998223ee3 FIX: quality inspection quick creation from purchase receipt: pre-fill sample size if available (#31544)
FIX: quality inspection quick creation from transaction documents: pre-fill sample size if available

Co-authored-by: Marica <maricadsouza221197@gmail.com>
(cherry picked from commit 048c037842)
2022-07-21 08:17:48 +00:00
mergify[bot]
794fd0819f fix: correct Brazilian portuguese translations (backport #31498) (#31660)
fix: correct Brazilian portuguese translations (#31498)

* fix brazilian portuguese translations

* minor adjustments

* fix minor adjustments

* fix: remove legacy pt_br.csv

* chore: Fix translation quotation marks

Co-authored-by: Marica <maricadsouza221197@gmail.com>
(cherry picked from commit d30f8387d9)

Co-authored-by: Marco Fonseca <62901164+treasuryesc@users.noreply.github.com>
2022-07-21 13:47:18 +05:30
Maharshi Patel
1d69ce1932 fix: (india) add overseas in HSN wise report
SQL query had WHERE `tabSales Taxes and Charges`.parent = `tabSales Invoice`.name. However, In overseas, there is no `tabSales Taxes and Charges` so I changed the query to use a combination of INNER JOIN and LEFT JOIN to fetch all entries.
2022-07-21 13:27:30 +05:30
Maharshi Patel
8e23c6ad69 fix: assign duplicate_items_msg outside conditional (#31639)
duplicate_items_msg was defined inside if the statement  of stock_item so when duplicate was found in non_stock_item it raised referenced before assignment
2022-07-20 21:25:36 +05:30
Frappe PR Bot
32ba8c7d6d chore(release): Bumped to Version 13.36.0
# [13.36.0](https://github.com/frappe/erpnext/compare/v13.35.3...v13.36.0) (2022-07-20)

### Bug Fixes

* (india) (e-invoice) allow generation for UIN Holders ([82539fc](82539fc18a))
* (india) (e-invoice) CN / DN with 0 qty ([84f260e](84f260e1d8))
* (india) (e-invoice) discount calculation ([1f4d434](1f4d434675))
* (india) (e-invoice) discount in CN/DN ([6e6b55e](6e6b55ece0))
* (india) (e-invoice) qty should not be changed to 1 ([b0efb98](b0efb98237))
* (india)(einvoice) discount for CN DN ([844758a](844758a27c))
* added Section translation in German for Salary Slip (backport [#31608](https://github.com/frappe/erpnext/issues/31608)) ([#31616](https://github.com/frappe/erpnext/issues/31616)) ([12d3d6a](12d3d6ab49))
* Allow multi currency invoice against single party account ([9ac9c46](9ac9c46dab))
* **India:** Inward supplies from Composition Supplier in 3B report ([b147ce4](b147ce4206))
* LCV updates wrong future qty/Bin qty ([f0ecdbe](f0ecdbef5a))
* linter ([9a3c846](9a3c84663f))
* move auto attendance job to long queue ([8eb9aaa](8eb9aaafe9))
* on cancel for loan refund ([8237d94](8237d94b11))
* Opening Invoice Creation Tool msgprint ([cb80bcd](cb80bcd0ee))
* Patch to make accounting dimension in orders ([55399f2](55399f2e58))
* Remove unnecessary list comprehensions ([711501b](711501be5e))
* Removed 'Allow Monthly Depreciation' checkbox and fixed wdv depreciation rate ([616e3c6](616e3c66b9))
* Renamed status field to gle_processing_status ([1180135](1180135067))
* Set Amount in Supplied Item table ([72e8c02](72e8c02ae0))
* set default_bom for item ([def622c](def622c13e))
* set status on submit/cancel ([04b077a](04b077a89f))
* slow stock reposting ([a5dae92](a5dae9264d))
* Supplier details in TDS monthly report ([#31599](https://github.com/frappe/erpnext/issues/31599)) ([431d79f](431d79f516))
* Tax amount not considered in Expense Claim Journal Entry ([90c751f](90c751f648))
* test ([9631ffd](9631ffd215))

### Features

* add adjustment amount to loan ([d5ce780](d5ce780e87))

### Performance Improvements

* index shift type and employee in checkins and assignment to avoid full table scans ([6938025](6938025952))
* Optimization of gl entry processing logic in period closing voucher ([903e42f](903e42fbdd))
* Replace `db.get_all` with `db.exists` in `is_holiday` ([85c554e](85c554eab5))
* Replace `get_doc` with `db.get_value` in `get_shift_details` ([abb7ac5](abb7ac5a0b))
* Use `get_cached_value` and `get_cached_doc` ([8c9035d](8c9035d914))
2022-07-20 15:21:11 +00:00
Deepesh Garg
4d67695294 Merge pull request #31647 from frappe/version-13-hotfix
chore: weekly version-13 release
2022-07-20 20:49:09 +05:30
Deepesh Garg
50fbfd9839 Merge pull request #31649 from frappe/mergify/bp/version-13-hotfix/pr-31648
fix: Patch to make accounting dimension in orders (backport #31648)
2022-07-20 20:01:27 +05:30
Deepesh Garg
e36c74ac44 chore: resolve conflicts 2022-07-20 19:39:18 +05:30
Deepesh Garg
45e25b2d56 chore: do not re run patch
(cherry picked from commit cad2035e07)

# Conflicts:
#	erpnext/patches.txt
2022-07-20 13:32:41 +00:00
Deepesh Garg
55399f2e58 fix: Patch to make accounting dimension in orders
(cherry picked from commit d46e406db7)

# Conflicts:
#	erpnext/patches.txt
2022-07-20 13:32:41 +00:00
Deepesh Garg
a5cd81c73c Merge branch 'version-13' into version-13-hotfix 2022-07-20 17:52:28 +05:30
Nabin Hait
52c8e0a6ea Merge pull request #31644 from nabinhait/asset-monthly-depreciation-v13-hotfix
fix: Removed 'Allow Monthly Depreciation' checkbox
2022-07-20 17:33:15 +05:30
Nabin Hait
616e3c66b9 fix: Removed 'Allow Monthly Depreciation' checkbox and fixed wdv depreciation rate 2022-07-20 17:32:28 +05:30
Nabin Hait
a6e97ea9ab Merge pull request #31626 from nabinhait/pcv-perf-enhancements
perf: Optimization of gl entry processing logic in period closing voucher
2022-07-20 15:39:39 +05:30
Deepesh Garg
2c5e117f01 Merge pull request #31629 from maharshivpatel/fix-einvoice-discounts
fix: (india) (e-invoice) convert discount to absolute value
2022-07-20 13:18:14 +05:30
Marica
a65dd58444 Merge pull request #31416 from frappe/mergify/bp/version-13-hotfix/pr-31412
fix: set default_bom for item (backport #31412)
2022-07-20 11:31:55 +05:30
Nabin Hait
04b077a89f fix: set status on submit/cancel 2022-07-20 11:10:36 +05:30
Nabin Hait
1180135067 fix: Renamed status field to gle_processing_status 2022-07-20 10:50:44 +05:30
Rucha Mahabal
eae4b06a80 Merge pull request #31630 from ruchamahabal/auto-attendance-perf 2022-07-20 10:25:02 +05:30
Maharshi Patel
b0efb98237 fix: (india) (e-invoice) qty should not be changed to 1
Qty of 0 is allowed so can't change item.qty to 1  instead created item_qty and used it.
2022-07-19 22:29:38 +05:30
rohitwaghchaure
e7e1847ec9 Merge pull request #31634 from frappe/mergify/bp/version-13-hotfix/pr-31631
fix: slow stock reposting (backport #31631)
2022-07-19 19:26:35 +05:30
Rohit Waghchaure
a5dae9264d fix: slow stock reposting
(cherry picked from commit 1d80d37ccf)
2022-07-19 13:18:13 +00:00
Marica
284095106c Merge pull request #31588 from SolufyPrivateLimited/Solufyin-oict
fix: Opening Invoice Creation Tool msgprint
2022-07-19 17:47:44 +05:30
Rucha Mahabal
8eb9aaafe9 fix: move auto attendance job to long queue 2022-07-19 16:07:18 +05:30
Rucha Mahabal
6938025952 perf: index shift type and employee in checkins and assignment to avoid full table scans 2022-07-19 16:02:31 +05:30
Maharshi Patel
6e6b55ece0 fix: (india) (e-invoice) discount in CN/DN
discount fixes
2022-07-19 15:31:34 +05:30
Rucha Mahabal
8c9035d914 perf: Use get_cached_value and get_cached_doc 2022-07-19 14:47:14 +05:30
Nabin Hait
cfb11f4b84 test: Added test for PCV cancellation 2022-07-19 14:31:02 +05:30
Marica
feffc3af27 Merge pull request #31627 from frappe/mergify/bp/version-13-hotfix/pr-31515
fix: LCV updates wrong future qty/Bin qty (backport #31515)
2022-07-19 14:21:33 +05:30
marination
ca9c6e1651 test: LCV impact on future stock balancees
(cherry picked from commit de9ea70ce3)
2022-07-19 08:10:22 +00:00
marination
f0ecdbef5a fix: LCV updates wrong future qty/Bin qty
- As -ve LCV SLE case is returned from `repost_current_voucher`, future qty is not updated
- This just doubly shifts all future qty which is then fixed by a repost
- Until the repost balance values are wrong
- Bin continues to show wrong projected qty even after repost, this is fixed by next SLE that recalculates Bin

(cherry picked from commit 7a5fd71a6c)
2022-07-19 08:10:21 +00:00
Marica
31930a16fa Merge pull request #31624 from marination/sub-contract-amount
fix: Set `amount` in Raw Supplied Items table
2022-07-19 12:55:20 +05:30
Deepesh Garg
4f55fef782 Merge pull request #31623 from frappe/mergify/bp/version-13-hotfix/pr-31599
fix: Supplier details in TDS monthly report (backport #31599)
2022-07-19 12:20:29 +05:30
marination
72e8c02ae0 fix: Set Amount in Supplied Item table 2022-07-19 11:47:25 +05:30
Deepesh Garg
f02596242c Merge pull request #31609 from deepeshgarg007/gstr_3b_gst_category
fix(India): Inward supplies from Composition Supplier in GST 3B report
2022-07-19 10:23:44 +05:30
Deepesh Garg
431d79f516 fix: Supplier details in TDS monthly report (#31599)
(cherry picked from commit a6ff4db2ec)
2022-07-19 04:45:28 +00:00
Rucha Mahabal
711501be5e fix: Remove unnecessary list comprehensions 2022-07-18 21:32:04 +05:30
Rucha Mahabal
abb7ac5a0b perf: Replace get_doc with db.get_value in get_shift_details 2022-07-18 21:13:02 +05:30
Rucha Mahabal
85c554eab5 perf: Replace db.get_all with db.exists in is_holiday
- widely used function call, fetching all rows for holidays
2022-07-18 21:08:15 +05:30
Nabin Hait
903e42fbdd perf: Optimization of gl entry processing logic in period closing voucher 2022-07-18 17:59:42 +05:30
mergify[bot]
12d3d6ab49 fix: added Section translation in German for Salary Slip (backport #31608) (#31616)
Co-authored-by: Wolfram Schmidt <wolfram.schmidt@phamos.eu>
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-07-18 13:41:42 +05:30
Rucha Mahabal
26497f9c0e Merge pull request #31614 from ruchamahabal/fix-claim-payment
fix: Tax amount not considered in Expense Claim Journal Entry
2022-07-18 13:24:12 +05:30
Rucha Mahabal
6a7549d4b2 test: Journal Entry against Expense Claim 2022-07-18 12:57:50 +05:30
Rucha Mahabal
90c751f648 fix: Tax amount not considered in Expense Claim Journal Entry
- add a helper function `get_outstanding_amount_for_claim` for uniformity in usages
2022-07-18 12:39:09 +05:30
Frappe PR Bot
1090862296 chore(release): Bumped to Version 13.35.3
## [13.35.3](https://github.com/frappe/erpnext/compare/v13.35.2...v13.35.3) (2022-07-18)

### Bug Fixes

* (india) (e-invoice) CN / DN with 0 qty and discount issues ([425abcd](425abcdea7))
2022-07-18 05:21:17 +00:00
mergify[bot]
425abcdea7 fix: (india) (e-invoice) CN / DN with 0 qty and discount issues
* fix: (india) (e-invoice) CN / DN with 0 qty
2022-07-18 10:49:41 +05:30
Deepesh Garg
144057f7e3 Merge pull request #31583 from maharshivpatel/fix-e_invoice-cn-dn-qty-0-error
fix: (india) (e-invoice) CN / DN with 0 qty and discount issues
2022-07-18 09:09:19 +05:30
Maharshi Patel
9631ffd215 fix: test
change setting to run test with correct use case.
2022-07-17 15:40:55 +05:30
Maharshi Patel
9a3c84663f fix: linter 2022-07-17 15:22:37 +05:30
Maharshi Patel
2066e5a53a discount settings for e-invoice added and test cases 2022-07-17 15:08:40 +05:30
Deepesh Garg
b147ce4206 fix(India): Inward supplies from Composition Supplier in 3B report 2022-07-17 00:43:25 +05:30
Deepesh Garg
0d6beed546 test: Update test case 2022-07-16 14:06:01 +05:30
Maharshi Patel
844758a27c fix: (india)(einvoice) discount for CN DN 2022-07-15 16:54:10 +05:30
Maharshi Patel
1f4d434675 fix: (india) (e-invoice) discount calculation
I have added fixes for discount in e-inovice.
2022-07-15 16:03:42 +05:30
Dany Robert
01d6df45d0 fix(india): e-way bill json for unregistered gst category 2022-07-15 11:17:41 +05:30
Deepesh Garg
8c7b836de8 Merge pull request #31595 from maharshivpatel/fix-uin-e_invoice
fix: (india) (e-invoice) allow generation for UIN Holders
2022-07-14 21:25:45 +05:30
Deepesh Garg
9fd1fad259 Merge pull request #31572 from frappe/mergify/bp/version-13-hotfix/pr-31566
fix: Allow multi currency invoice against single party account (backport #31566)
2022-07-14 21:25:17 +05:30
Deepesh Garg
dfa0638baf Merge pull request #31577 from frappe/mergify/bp/version-13-hotfix/pr-31543
feat: Loan balance adjustment doctypes (backport #31543)
2022-07-14 21:24:49 +05:30
Deepesh Garg
0a5bc86d1b chore: resolve conflicts 2022-07-14 17:20:32 +05:30
Deepesh Garg
bdd15895ff chore: resolve conflicts 2022-07-14 17:06:01 +05:30
Maharshi Patel
82539fc18a fix: (india) (e-invoice) allow generation for UIN Holders
e-invoice is required for UIN Holders and they should be treated as Registered Regular. there was incorrect if hasattr check that prevented UIN number validation.
2022-07-14 15:12:51 +05:30
Solufyin
cb80bcd0ee fix: Opening Invoice Creation Tool msgprint 2022-07-13 11:55:47 +05:30
Maharshi Patel
84f260e1d8 fix: (india) (e-invoice) CN / DN with 0 qty
Qty 0 is allowed when creating Credit or Debit Note this caused ZeroDivisionError during unit_rate calculation.
fixed the issue by adding required conditionals.
2022-07-13 01:39:36 +05:30
Sagar Sharma
023c5db3bc Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-31412 2022-07-12 23:33:48 +05:30
Deepesh Garg
0b3958d692 chore: Linting Issues
(cherry picked from commit 0bac030ca7)
2022-07-12 12:56:46 +00:00
Deepesh Garg
c2a7dc7754 chore: Linting Issues
(cherry picked from commit d933ff5cf6)
2022-07-12 12:56:46 +00:00
Abhinav Raut
8237d94b11 fix: on cancel for loan refund
(cherry picked from commit 13b7ed1e2c)
2022-07-12 12:56:45 +00:00
Labeeb Mattra
0713a240a7 Consider refund_amount in pending principal amount
(cherry picked from commit 35f2717ad2)
2022-07-12 12:56:45 +00:00
Labeeb Mattra
61b2a1c3cb fix excess amount calculation in loan refund
(cherry picked from commit 9df1413adb)
2022-07-12 12:56:44 +00:00
Labeeb Mattra
94be7a95f7 Update adjustment_type field options
(cherry picked from commit 245b0c7818)
2022-07-12 12:56:44 +00:00
Labeeb Mattra
7ba0326bdd fix adjustment amount field name
(cherry picked from commit 6cc09ef3a2)
2022-07-12 12:56:43 +00:00
Labeeb Mattra
86af1ef6ed fix indent and imports
(cherry picked from commit 0ed6382ab6)
2022-07-12 12:56:43 +00:00
Labeeb Mattra
b995338d0c fix lint
(cherry picked from commit 8434ec09c3)
2022-07-12 12:56:43 +00:00
Labeeb Mattra
0856e14c13 Use adjustment amounts in pending principal amnt
(cherry picked from commit 1b5b2138ee)
2022-07-12 12:56:42 +00:00
Labeeb Mattra
ff5ee2bbac Add ref no to balance adjustment remarks
(cherry picked from commit a1a51ce1a6)
2022-07-12 12:56:42 +00:00
Labeeb Mattra
a36e8a83d8 Add reference number to repayment remarks
(cherry picked from commit 74dbf8c5d9)

# Conflicts:
#	erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
2022-07-12 12:56:41 +00:00
Labeeb Mattra
d81e7e2421 Add NPA checkbox in Loan
(cherry picked from commit 27a8e16b28)
2022-07-12 12:56:41 +00:00
Labeeb Mattra
8181d61293 Update list view for Accrual and Shortfall
(cherry picked from commit 7d6e4898c4)
2022-07-12 12:56:40 +00:00
Labeeb Mattra
548aac5b11 Remove loan account field from doctype
(cherry picked from commit 6febcd529b)
2022-07-12 12:56:40 +00:00
Labeeb Mattra
de69fee6aa Use new adjustment amount fields
(cherry picked from commit 7d468a8778)
2022-07-12 12:56:39 +00:00
Labeeb Mattra
c3e6d7ba29 Seperate credit and debit adjust amount fields in Loan
(cherry picked from commit d6f632a770)
2022-07-12 12:56:38 +00:00
Abhinav Raut
d5ce780e87 feat: add adjustment amount to loan
- fix: bugs in loan balance adjustment

(cherry picked from commit 5c0a25012c)
2022-07-12 12:56:38 +00:00
Labeeb Mattra
a2125e694d Add Loan Balance Adjustment doctype
(cherry picked from commit 2e8f056514)
2022-07-12 12:56:37 +00:00
Labeeb Mattra
0d94653c5b Add Loan Refund doctype
(cherry picked from commit e1682965c5)
2022-07-12 12:56:36 +00:00
Labeeb Mattra
d9723a12c4 Add refund amount to loan
(cherry picked from commit 88cd780ca1)
2022-07-12 12:56:36 +00:00
Labeeb Mattra
d24cb01a9d Add more loan interest accrual types
(cherry picked from commit a81da2ea85)
2022-07-12 12:56:35 +00:00
Labeeb Mattra
ab214bcdfc update loan interest accrual types
(cherry picked from commit 900c878e03)
2022-07-12 12:56:35 +00:00
Frappe PR Bot
751fbd6794 chore(release): Bumped to Version 13.35.2
## [13.35.2](https://github.com/frappe/erpnext/compare/v13.35.1...v13.35.2) (2022-07-12)

### Bug Fixes

* components in the same table don't get updated value of prev payment-days based component ([01beb6f](01beb6f391))
* conflicts ([2045df1](2045df19f9))
* Incorrect provisional expense booking while reposting ([d182137](d182137ed1))
* timeout error while reposting ([07b80c2](07b80c295d))
* Use Contact Name instead of Supplier in RFQ Email ([b0e17de](b0e17dea2a))
* Use fallback conversion factor while setting incoming rate for petty purchase ([2a432c2](2a432c22d4))
* Validate payment-days-based dependent component ([a28c7cf](a28c7cf094))
2022-07-12 09:29:26 +00:00
Deepesh Garg
36566c1d14 Merge pull request #31574 from frappe/version-13-hotfix
chore: weekly version-13 release
2022-07-12 14:57:39 +05:30
Rucha Mahabal
28e4e4320e Merge pull request #31521 from ruchamahabal/fix-salary-calc-pd
fix: components in the same table don't get updated value of prev payment-days based component
2022-07-12 12:06:21 +05:30
Rucha Mahabal
1133062bfc Merge branch 'version-13-hotfix' into fix-salary-calc-pd 2022-07-12 11:42:07 +05:30
Rucha Mahabal
a28c7cf094 fix: Validate payment-days-based dependent component 2022-07-12 11:39:00 +05:30
Deepesh Garg
1c686c732e chore: fix query
(cherry picked from commit e04e67c6bf)
2022-07-11 15:44:49 +00:00
Deepesh Garg
66c5290dee chore: Ignore validation
(cherry picked from commit 3cf609fab1)
2022-07-11 15:44:49 +00:00
Deepesh Garg
9ac9c46dab fix: Allow multi currency invoice against single party account
(cherry picked from commit c83fbd5c50)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
2022-07-11 15:44:48 +00:00
Sagar Sharma
faa489bc73 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-31412 2022-07-11 17:11:21 +05:30
Sagar Sharma
b17850ac3e chore: conflicts 2022-07-11 17:09:30 +05:30
Marica
7d36bdf292 Merge pull request #31565 from frappe/mergify/bp/version-13-hotfix/pr-31550
fix: Use Contact Name instead of Supplier in RFQ Email (backport #31550)
2022-07-11 12:35:18 +05:30
marination
b331c462ef chore: Instantiate variable unconditionally
(cherry picked from commit 300e812a1f)
2022-07-11 05:52:08 +00:00
marination
b0e17dea2a fix: Use Contact Name instead of Supplier in RFQ Email
(cherry picked from commit 88ac519b24)
2022-07-11 05:52:07 +00:00
Frappe PR Bot
be3b010b44 chore(release): Bumped to Version 13.35.1
## [13.35.1](https://github.com/frappe/erpnext/compare/v13.35.0...v13.35.1) (2022-07-08)

### Bug Fixes

* Use fallback conversion factor while setting incoming rate for petty purchase ([bac8546](bac854628f))
2022-07-08 10:20:30 +00:00
Deepesh Garg
45799f51b3 Merge pull request #31558 from frappe/mergify/bp/version-13/pr-31529
fix: Use fallback conversion factor while setting incoming rate for petty purchase (backport #31529)
2022-07-08 15:48:47 +05:30
marination
bac854628f fix: Use fallback conversion factor while setting incoming rate for petty purchase
- PIs for petty items (that do not need an Item record) are allowed using Item Name field
- If a different UOM is used in this case, conversion factor stays 0 and causes an error
- Fallback to 1 in `set_incoming_rate` for buying
- Selling will need a proper item, so this change is not needed there

(cherry picked from commit aa043fe961)
2022-07-08 09:47:05 +00:00
Deepesh Garg
6d04bafb04 Merge pull request #31518 from frappe/mergify/bp/version-13-hotfix/pr-31516
fix: Incorrect provisional expense booking while reposting (backport #31516)
2022-07-07 11:53:58 +05:30
rohitwaghchaure
c4f39c7b8b Merge pull request #31538 from frappe/mergify/bp/version-13-hotfix/pr-31519
fix: timeout error while reposting (backport #31519)
2022-07-06 14:33:26 +05:30
rohitwaghchaure
2045df19f9 fix: conflicts 2022-07-06 12:34:10 +05:30
Rohit Waghchaure
07b80c295d fix: timeout error while reposting
(cherry picked from commit 78c8bb251e)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/test_stock_entry.py
2022-07-06 07:02:31 +00:00
Marica
a8c3882400 Merge pull request #31534 from frappe/mergify/bp/version-13-hotfix/pr-31529
fix: Use fallback conversion factor while setting incoming rate for petty purchase (backport #31529)
2022-07-06 01:20:51 +05:30
marination
2a432c22d4 fix: Use fallback conversion factor while setting incoming rate for petty purchase
- PIs for petty items (that do not need an Item record) are allowed using Item Name field
- If a different UOM is used in this case, conversion factor stays 0 and causes an error
- Fallback to 1 in `set_incoming_rate` for buying
- Selling will need a proper item, so this change is not needed there

(cherry picked from commit aa043fe961)
2022-07-05 19:09:36 +00:00
Deepesh Garg
776e807ade Merge pull request #31530 from vorasmit/warn-remove-india
chore: deprecation warning for remove-india
2022-07-05 22:46:33 +05:30
Smit Vora
6bda2a0865 chore: deprecation warning for remove-india 2022-07-05 21:20:51 +05:30
Rucha Mahabal
01beb6f391 fix: components in the same table don't get updated value of prev payment-days based component 2022-07-04 23:14:26 +05:30
Deepesh Garg
d182137ed1 fix: Incorrect provisional expense booking while reposting
(cherry picked from commit 60aad31162)
2022-07-04 14:58:41 +00:00
Deepesh Garg
fecf567e92 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-31412 2022-06-30 16:46:59 +05:30
Sagar Sharma
96bd493a6f Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-31412 2022-06-24 13:17:43 +05:30
s-aga-r
def622c13e fix: set default_bom for item
(cherry picked from commit dc2830da4d)

# Conflicts:
#	erpnext/manufacturing/doctype/bom/test_bom.py
2022-06-21 11:13:31 +00:00
119 changed files with 12987 additions and 7380 deletions

View File

@@ -10,7 +10,7 @@
"@semantic-release/release-notes-generator",
[
"@semantic-release/exec", {
"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" erpnext/__init__.py'
"prepareCmd": 'sed -ir -E "s/\"[0-9]+\.[0-9]+\.[0-9]+\"/\"${nextRelease.version}\"/" erpnext/__init__.py'
}
],
[

View File

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

View File

@@ -19,6 +19,7 @@
"book_asset_depreciation_entry_automatically",
"unlink_advance_payment_on_cancelation_of_order",
"enable_common_party_accounting",
"allow_multi_currency_invoices_against_single_party_account",
"post_change_gl_entries",
"enable_discount_accounting",
"tax_settings_section",
@@ -276,14 +277,21 @@
"fieldname": "enable_common_party_accounting",
"fieldtype": "Check",
"label": "Enable Common Party Accounting"
}
},
{
"default": "0",
"description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency",
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
"fieldtype": "Check",
"label": "Allow multi-currency invoices against single party account"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-10-11 17:42:36.427699",
"modified": "2022-07-11 13:37:50.605141",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -43,7 +43,7 @@ class GLEntry(Document):
self.validate_and_set_fiscal_year()
self.pl_must_have_cost_center()
if not self.flags.from_repost:
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
self.check_mandatory()
self.validate_cost_center()
self.check_pl_account()
@@ -52,7 +52,7 @@ class GLEntry(Document):
def on_update(self):
adv_adj = self.flags.adv_adj
if not self.flags.from_repost:
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()

View File

@@ -25,7 +25,10 @@ from erpnext.accounts.utils import (
get_stock_and_account_balance,
)
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
from erpnext.hr.doctype.expense_claim.expense_claim import (
get_outstanding_amount_for_claim,
update_reimbursed_amount,
)
class StockAccountInvalidTransaction(frappe.ValidationError):
@@ -935,15 +938,12 @@ class JournalEntry(AccountsController):
def validate_expense_claim(self):
for d in self.accounts:
if d.reference_type == "Expense Claim":
sanctioned_amount, reimbursed_amount = frappe.db.get_value(
"Expense Claim", d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed")
)
pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount)
if d.debit > pending_amount:
outstanding_amt = get_outstanding_amount_for_claim(d.reference_name)
if d.debit > outstanding_amt:
frappe.throw(
_(
"Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}"
).format(d.idx, d.reference_name, pending_amount)
"Row No {0}: Amount cannot be greater than the Outstanding Amount against Expense Claim {1}. Outstanding Amount is {2}"
).format(d.idx, d.reference_name, outstanding_amt)
)
def validate_credit_debit_note(self):

View File

@@ -49,7 +49,15 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
doc: frm.doc,
btn: $(btn_primary),
method: "make_invoices",
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type])
freeze: 1,
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
callback: function(r) {
if (r.message.length == 1) {
frappe.msgprint(__("{0} Invoice created successfully.", [frm.doc.invoice_type]));
} else if (r.message.length < 50) {
frappe.msgprint(__("{0} Invoices created successfully.", [frm.doc.invoice_type]));
}
}
});
});

View File

@@ -30,7 +30,10 @@ from erpnext.controllers.accounts_controller import (
get_supplier_block_status,
validate_taxes_and_charges,
)
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
from erpnext.hr.doctype.expense_claim.expense_claim import (
get_outstanding_amount_for_claim,
update_reimbursed_amount,
)
from erpnext.setup.utils import get_exchange_rate
@@ -302,7 +305,7 @@ class PaymentEntry(AccountsController):
def validate_reference_documents(self):
if self.party_type == "Student":
valid_reference_doctypes = "Fees"
valid_reference_doctypes = ("Fees", "Journal Entry")
elif self.party_type == "Customer":
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
elif self.party_type == "Supplier":
@@ -1649,12 +1652,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
outstanding_amount = (
flt(ref_doc.get("total_sanctioned_amount"))
+ flt(ref_doc.get("total_taxes_and_charges"))
- flt(ref_doc.get("total_amount_reimbursed"))
- flt(ref_doc.get("total_advance_amount"))
)
outstanding_amount = get_outstanding_amount_for_claim(ref_doc)
elif reference_doctype == "Employee Advance":
outstanding_amount = flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount)
if party_account_currency != ref_doc.currency:

View File

@@ -10,10 +10,11 @@
"fiscal_year",
"amended_from",
"company",
"cost_center_wise_pnl",
"column_break1",
"closing_account_head",
"remarks"
"remarks",
"gle_processing_status",
"error_message"
],
"fields": [
{
@@ -86,17 +87,26 @@
"reqd": 1
},
{
"default": "0",
"fieldname": "cost_center_wise_pnl",
"fieldtype": "Check",
"label": "Book Cost Center Wise Profit/Loss"
"depends_on": "eval:doc.docstatus!=0",
"fieldname": "gle_processing_status",
"fieldtype": "Select",
"label": "GL Entry Processing Status",
"options": "In Progress\nCompleted\nFailed",
"read_only": 1
},
{
"depends_on": "eval:doc.gle_processing_status=='Failed'",
"fieldname": "error_message",
"fieldtype": "Text",
"label": "Error Message",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-05-20 15:27:37.210458",
"modified": "2022-07-20 14:51:04.714154",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Period Closing Voucher",

View File

@@ -8,7 +8,6 @@ from frappe.utils import flt
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimensions,
)
from erpnext.accounts.utils import get_account_currency
from erpnext.controllers.accounts_controller import AccountsController
@@ -20,13 +19,28 @@ class PeriodClosingVoucher(AccountsController):
self.validate_posting_date()
def on_submit(self):
self.db_set("gle_processing_status", "In Progress")
self.make_gl_entries()
def on_cancel(self):
self.db_set("gle_processing_status", "In Progress")
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
from erpnext.accounts.general_ledger import make_reverse_gl_entries
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
gle_count = frappe.db.count(
"GL Entry",
{"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0},
)
if gle_count > 5000:
frappe.enqueue(
make_reverse_gl_entries,
voucher_type="Period Closing Voucher",
voucher_no=self.name,
queue="long",
)
frappe.msgprint(
_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True
)
else:
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
def validate_account_head(self):
closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type")
@@ -67,90 +81,80 @@ class PeriodClosingVoucher(AccountsController):
def make_gl_entries(self):
gl_entries = self.get_gl_entries()
if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries)
if len(gl_entries) > 5000:
frappe.enqueue(process_gl_entries, gl_entries=gl_entries, queue="long")
frappe.msgprint(
_("The GL Entries will be processed in the background, it can take a few minutes."),
alert=True,
)
else:
process_gl_entries(gl_entries)
def get_gl_entries(self):
gl_entries = []
pl_accounts = self.get_pl_balances()
for acc in pl_accounts:
# pl account
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True):
if flt(acc.bal_in_company_currency):
gl_entries.append(
self.get_gl_dict(
{
"account": acc.account,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) < 0
else 0,
"debit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) < 0
else 0,
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) > 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) > 0
else 0,
},
item=acc,
)
)
gl_entries.append(self.get_gle_for_pl_account(acc))
if gl_entries:
gle_for_net_pl_bal = self.get_pnl_gl_entry(pl_accounts)
gl_entries += gle_for_net_pl_bal
# closing liability account
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=False):
if flt(acc.bal_in_company_currency):
gl_entries.append(self.get_gle_for_closing_account(acc))
return gl_entries
def get_pnl_gl_entry(self, pl_accounts):
company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
gl_entries = []
def get_gle_for_pl_account(self, acc):
gl_entry = self.get_gl_dict(
{
"account": acc.account,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) < 0
else 0,
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) > 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
},
item=acc,
)
self.update_default_dimensions(gl_entry, acc)
return gl_entry
for acc in pl_accounts:
if flt(acc.bal_in_company_currency):
cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center
gl_entry = self.get_gl_dict(
{
"account": self.closing_account_head,
"cost_center": cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) > 0
else 0,
"debit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) > 0
else 0,
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) < 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) < 0
else 0,
},
item=acc,
)
def get_gle_for_closing_account(self, acc):
gl_entry = self.get_gl_dict(
{
"account": self.closing_account_head,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) > 0
else 0,
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) < 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
},
item=acc,
)
self.update_default_dimensions(gl_entry, acc)
return gl_entry
self.update_default_dimensions(gl_entry)
gl_entries.append(gl_entry)
return gl_entries
def update_default_dimensions(self, gl_entry):
def update_default_dimensions(self, gl_entry, acc):
if not self.accounting_dimensions:
self.accounting_dimensions = get_accounting_dimensions()
_, default_dimensions = get_dimensions()
for dimension in self.accounting_dimensions:
gl_entry.update({dimension: default_dimensions.get(self.company, {}).get(dimension)})
gl_entry.update({dimension: acc.get(dimension)})
def get_pl_balances(self):
def get_pl_balances_based_on_dimensions(self, group_by_account=False):
"""Get balance for dimension-wise pl accounts"""
dimension_fields = ["t1.cost_center", "t1.finance_book"]
@@ -159,20 +163,56 @@ class PeriodClosingVoucher(AccountsController):
for dimension in self.accounting_dimensions:
dimension_fields.append("t1.{0}".format(dimension))
if group_by_account:
dimension_fields.append("t1.account")
return frappe.db.sql(
"""
select
t1.account, t2.account_currency, {dimension_fields},
t2.account_currency,
{dimension_fields},
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2
where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2 and t2.company = %s
and t1.posting_date between %s and %s
group by t1.account, {dimension_fields}
where
t1.is_cancelled = 0
and t1.account = t2.name
and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2
and t2.company = %s
and t1.posting_date between %s and %s
group by {dimension_fields}
""".format(
dimension_fields=", ".join(dimension_fields)
),
(self.company, self.get("year_start_date"), self.posting_date),
as_dict=1,
)
def process_gl_entries(gl_entries):
from erpnext.accounts.general_ledger import make_gl_entries
try:
make_gl_entries(gl_entries, merge_entries=False)
frappe.db.set_value(
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
)
except Exception as e:
frappe.db.rollback()
frappe.log_error(e)
frappe.db.set_value(
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed"
)
def make_reverse_gl_entries(voucher_type, voucher_no):
from erpnext.accounts.general_ledger import make_reverse_gl_entries
try:
make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no)
frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Completed")
except Exception as e:
frappe.db.rollback()
frappe.log_error(e)
frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Failed")

View File

@@ -49,7 +49,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
expected_gle = (
("Cost of Goods Sold - TPC", 0.0, 600.0),
(surplus_account, 600.0, 400.0),
(surplus_account, 200.0, 0.0),
("Sales - TPC", 400.0, 0.0),
)
@@ -59,7 +59,8 @@ class TestPeriodClosingVoucher(unittest.TestCase):
""",
(pcv.name),
)
pcv.reload()
self.assertEqual(pcv.gle_processing_status, "Completed")
self.assertEqual(pcv_gle, expected_gle)
def test_cost_center_wise_posting(self):
@@ -94,7 +95,6 @@ class TestPeriodClosingVoucher(unittest.TestCase):
)
pcv = self.make_period_closing_voucher(submit=False)
pcv.cost_center_wise_pnl = 1
pcv.save()
pcv.submit()
surplus_account = pcv.closing_account_head
@@ -117,6 +117,16 @@ class TestPeriodClosingVoucher(unittest.TestCase):
self.assertEqual(pcv_gle, expected_gle)
pcv.reload()
pcv.cancel()
self.assertFalse(
frappe.db.get_value(
"GL Entry",
{"voucher_type": "Period Closing Voucher", "voucher_no": pcv.name, "is_cancelled": 0},
)
)
def test_period_closing_with_finance_book_entries(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")

View File

@@ -222,9 +222,6 @@ class POSInvoice(SalesInvoice):
allow_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
for d in self.get("items"):
is_service_item = not (frappe.db.get_value("Item", d.get("item_code"), "is_stock_item"))
if is_service_item:
return
if d.serial_no:
self.validate_pos_reserved_serial_nos(d)
self.validate_delivered_serial_nos(d)

View File

@@ -9,7 +9,7 @@ from frappe import _
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.model.document import Document
from frappe.model.mapper import map_child_doc, map_doc
from frappe.utils import flt, getdate, nowdate
from frappe.utils import cint, flt, getdate, nowdate
from frappe.utils.background_jobs import enqueue
from frappe.utils.scheduler import is_scheduler_inactive
@@ -219,6 +219,9 @@ class POSInvoiceMergeLog(Document):
invoice.taxes_and_charges = None
invoice.ignore_pricing_rule = 1
invoice.customer = self.customer
invoice.disable_rounded_total = cint(
frappe.db.get_value("POS Profile", invoice.pos_profile, "disable_rounded_total")
)
if self.merge_invoices_based_on == "Customer Group":
invoice.flags.ignore_pos_profile = True

View File

@@ -44,6 +44,7 @@
"write_off_account",
"write_off_cost_center",
"account_for_change_amount",
"disable_rounded_total",
"column_break_23",
"income_account",
"expense_account",
@@ -358,6 +359,13 @@
"fieldname": "validate_stock_on_save",
"fieldtype": "Check",
"label": "Validate Stock on Save"
},
{
"default": "0",
"description": "If enabled, the consolidated invoices will have rounded total disabled",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"label": "Disable Rounded Total"
}
],
"icon": "icon-cog",
@@ -385,7 +393,7 @@
"link_fieldname": "pos_profile"
}
],
"modified": "2022-03-21 13:29:28.480533",
"modified": "2022-07-21 11:16:46.911173",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@@ -158,6 +158,7 @@ class PurchaseInvoice(BuyingController):
if tds_category and not for_validate:
self.apply_tds = 1
self.tax_withholding_category = tds_category
self.set_onload("supplier_tds", tds_category)
super(PurchaseInvoice, self).set_missing_values(for_validate)

View File

@@ -414,7 +414,7 @@
},
{
"default": "0",
"depends_on": "eval: doc.is_return && doc.return_against",
"depends_on": "eval: doc.is_return",
"fieldname": "update_billed_amount_in_sales_order",
"fieldtype": "Check",
"hide_days": 1,
@@ -2046,7 +2046,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2022-06-16 16:22:44.870575",
"modified": "2022-07-11 17:43:56.435382",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -2173,13 +2173,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
source_document_warehouse_field = "target_warehouse"
target_document_warehouse_field = "from_warehouse"
received_items = get_received_items(source_name, target_doctype, target_detail_field)
else:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
source_document_warehouse_field = "from_warehouse"
target_document_warehouse_field = "target_warehouse"
received_items = get_received_items(source_name, target_doctype, target_detail_field)
received_items = {}
validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype)

View File

@@ -2623,20 +2623,34 @@ class TestSalesInvoice(unittest.TestCase):
def test_einvoice_discounts(self):
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
frappe.db.set_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice", False)
# Normal Itemized Discount
si = get_sales_invoice_for_e_invoice()
si.apply_discount_on = ""
si.items[0].discount_amount = 4000
si.items[1].discount_amount = 300
si.items[0].price_list_rate = 12
si.items[0].discount_percentage = 16.6666666667
si.items[0].rate = 10
si.items[1].price_list_rate = 15
si.items[1].discount_amount = 5
si.items[1].rate = 10
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
self.assertEqual(einvoice["ItemList"][0]["Discount"], 4000)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 300)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 2100)
self.assertEqual(einvoice["ItemList"][2]["Discount"], 222)
self.assertEqual(einvoice["ItemList"][3]["Discount"], 5555)
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15)
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20)
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10)
# Invoice Discount on net total
si = get_sales_invoice_for_e_invoice()
si.apply_discount_on = "Net Total"
@@ -2646,10 +2660,17 @@ class TestSalesInvoice(unittest.TestCase):
einvoice = make_einvoice(si)
validate_totals(einvoice)
self.assertEqual(einvoice["ItemList"][0]["Discount"], 316.83)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 83.17)
self.assertEqual(einvoice["ItemList"][0]["Discount"], 253.61)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 66.57)
self.assertEqual(einvoice["ItemList"][2]["Discount"], 243.11)
self.assertEqual(einvoice["ItemList"][3]["Discount"], 5613.71)
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15)
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20)
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10)
# Invoice Discount on grand total (Itemized Discount)
si = get_sales_invoice_for_e_invoice()
si.apply_discount_on = "Grand Total"
@@ -2659,10 +2680,17 @@ class TestSalesInvoice(unittest.TestCase):
einvoice = make_einvoice(si)
validate_totals(einvoice)
self.assertEqual(einvoice["ItemList"][0]["Discount"], 268.5)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 70.48)
self.assertEqual(einvoice["ItemList"][0]["Discount"], 214.93)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 56.42)
self.assertEqual(einvoice["ItemList"][2]["Discount"], 239.89)
self.assertEqual(einvoice["ItemList"][3]["Discount"], 5604.75)
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15)
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20)
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10)
# Invoice Discount on grand total (Cash/Non-Trade Discount)
si = get_sales_invoice_for_e_invoice()
si.apply_discount_on = "Grand Total"
@@ -2675,8 +2703,133 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(einvoice["ItemList"][0]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][2]["Discount"], 222.0)
self.assertEqual(einvoice["ItemList"][3]["Discount"], 5555.0)
self.assertEqual(einvoice["ValDtls"]["Discount"], 400)
self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15)
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20)
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10)
si = get_sales_invoice_for_e_invoice()
si.apply_discount_on = ""
si.items[1].price_list_rate = 15
si.items[1].discount_amount = -5
si.items[1].rate = 20
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 20)
def test_einvoice_without_discounts(self):
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
frappe.db.set_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice", True)
# Normal Itemized Discount
si = get_sales_invoice_for_e_invoice()
si.apply_discount_on = ""
si.items[0].price_list_rate = 12
si.items[0].discount_percentage = 16.6666666667
si.items[0].rate = 10
si.items[1].price_list_rate = 15
si.items[1].discount_amount = 5
si.items[1].rate = 10
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
self.assertEqual(einvoice["ItemList"][0]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][2]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][3]["Discount"], 0)
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 10)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 10)
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 18)
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 5)
# Invoice Discount on net total
si = get_sales_invoice_for_e_invoice()
si.apply_discount_on = "Net Total"
si.discount_amount = 400
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
self.assertEqual(einvoice["ItemList"][0]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][2]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][3]["Discount"], 0)
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 11.87)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 14.84)
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 17.81)
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 4.95)
# Invoice Discount on grand total (Itemized Discount)
si = get_sales_invoice_for_e_invoice()
si.apply_discount_on = "Grand Total"
si.discount_amount = 400
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
self.assertEqual(einvoice["ItemList"][0]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][2]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][3]["Discount"], 0)
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 11.89)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 14.87)
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 17.84)
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 4.96)
# Invoice Discount on grand total (Cash/Non-Trade Discount)
si = get_sales_invoice_for_e_invoice()
si.apply_discount_on = "Grand Total"
si.is_cash_or_non_trade_discount = 1
si.discount_amount = 400
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
self.assertEqual(einvoice["ItemList"][0]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][2]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][3]["Discount"], 0)
self.assertEqual(einvoice["ValDtls"]["Discount"], 400)
self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15)
self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 18)
self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 5)
si = get_sales_invoice_for_e_invoice()
si.apply_discount_on = ""
si.items[1].price_list_rate = 15
si.items[1].discount_amount = -5
si.items[1].rate = 20
si.save()
einvoice = make_einvoice(si)
validate_totals(einvoice)
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 20)
def test_item_tax_net_range(self):
item = create_item("T Shirt")
@@ -3276,6 +3429,36 @@ def get_sales_invoice_for_e_invoice():
},
)
si.append(
"items",
{
"item_code": "_Test Item",
"uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
"qty": 111,
"price_list_rate": 20,
"discount_percentage": 10,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
si.append(
"items",
{
"item_code": "_Test Item 2",
"uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
"qty": 1111,
"price_list_rate": 10,
"rate": 5,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
return si

View File

@@ -148,6 +148,7 @@ def update_net_values(entry):
def merge_similar_entries(gl_map, precision=None):
merged_gl_map = []
accounting_dimensions = get_accounting_dimensions()
for entry in gl_map:
# if there is already an entry in this account then just add it
# to that entry
@@ -229,9 +230,10 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
gle.flags.from_repost = from_repost
gle.flags.adv_adj = adv_adj
gle.flags.update_outstanding = update_outstanding or "Yes"
gle.flags.notify_update = False
gle.submit()
if not from_repost:
if not from_repost and gle.voucher_type != "Period Closing Voucher":
validate_expense_against_budget(args)

View File

@@ -44,14 +44,14 @@ frappe.query_reports["Gross Profit"] = {
"parent_field": "parent_invoice",
"initial_depth": 3,
"formatter": function(value, row, column, data, default_formatter) {
if (column.fieldname == "sales_invoice" && column.options == "Item" && data.indent == 0) {
if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) {
column._options = "Sales Invoice";
} else {
column._options = "Item";
}
value = default_formatter(value, row, column, data);
if (data && (data.indent == 0.0 || row[1].content == "Total")) {
if (data && (data.indent == 0.0 || (row[1] && row[1].content == "Total"))) {
value = $(`<span>${value}</span>`);
var $value = $(value).css("font-weight", "bold");
value = $value.wrap("<p></p>").parent().html();

View File

@@ -14,9 +14,9 @@ def execute(filters=None):
filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
columns = get_columns(filters)
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
final_result = group_by_supplier_and_category(res)
return columns, final_result

View File

@@ -8,11 +8,11 @@ from frappe import _
def execute(filters=None):
validate_filters(filters)
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
columns = get_columns(filters)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
return columns, res
@@ -22,7 +22,7 @@ def validate_filters(filters):
frappe.throw(_("From Date must be before To Date"))
def get_result(filters, tds_docs, tds_accounts, tax_category_map):
def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map):
supplier_map = get_supplier_pan_map()
tax_rate_map = get_tax_rate_map(filters)
gle_map = get_gle_map(tds_docs)
@@ -38,6 +38,11 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map):
posting_date = entry.posting_date
voucher_type = entry.voucher_type
if voucher_type == "Journal Entry":
suppliers = journal_entry_party_map.get(name)
if suppliers:
supplier = suppliers[0]
if not tax_withholding_category:
tax_withholding_category = supplier_map.get(supplier, {}).get("tax_withholding_category")
rate = tax_rate_map.get(tax_withholding_category)
@@ -176,6 +181,7 @@ def get_tds_docs(filters):
journal_entries = []
tax_category_map = {}
or_filters = {}
journal_entry_party_map = {}
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
tds_accounts = frappe.get_all(
@@ -218,9 +224,24 @@ def get_tds_docs(filters):
get_tax_category_map(payment_entries, "Payment Entry", tax_category_map)
if journal_entries:
journal_entry_party_map = get_journal_entry_party_map(journal_entries)
get_tax_category_map(journal_entries, "Journal Entry", tax_category_map)
return tds_documents, tds_accounts, tax_category_map
return tds_documents, tds_accounts, tax_category_map, journal_entry_party_map
def get_journal_entry_party_map(journal_entries):
journal_entry_party_map = {}
for d in frappe.db.get_all(
"Journal Entry Account",
{"parent": ("in", journal_entries), "party_type": "Supplier", "party": ("is", "set")},
["parent", "party"],
):
if d.parent not in journal_entry_party_map:
journal_entry_party_map[d.parent] = []
journal_entry_party_map[d.parent].append(d.party)
return journal_entry_party_map
def get_tax_category_map(vouchers, doctype, tax_category_map):

View File

@@ -38,7 +38,6 @@
"purchase_date",
"section_break_23",
"calculate_depreciation",
"allow_monthly_depreciation",
"column_break_33",
"opening_accumulated_depreciation",
"number_of_depreciations_booked",
@@ -454,13 +453,6 @@
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "calculate_depreciation",
"fieldname": "allow_monthly_depreciation",
"fieldtype": "Check",
"label": "Allow Monthly Depreciation"
},
{
"collapsible": 1,
"collapsible_depends_on": "is_existing_asset",
@@ -503,7 +495,7 @@
"link_fieldname": "asset"
}
],
"modified": "2022-01-30 20:19:24.680027",
"modified": "2022-07-20 16:22:44.437579",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
@@ -545,4 +537,4 @@
"sort_order": "DESC",
"title_field": "asset_name",
"track_changes": 1
}
}

View File

@@ -353,61 +353,16 @@ class Asset(AccountsController):
skip_row = True
if depreciation_amount > 0:
# With monthly depreciation, each depreciation is divided by months remaining until next date
if self.allow_monthly_depreciation:
# month range is 1 to 12
# In pro rata case, for first and last depreciation, month range would be different
if (has_pro_rata and n == 0 and not self.number_of_depreciations_booked) or (
has_pro_rata and n == cint(number_of_pending_depreciations) - 1
):
month_range = months
else:
month_range = finance_book.frequency_of_depreciation
for r in range(month_range):
if has_pro_rata and n == 0 and not self.number_of_depreciations_booked:
# For first entry of monthly depr
if r == 0:
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date) + 1
per_day_amt = depreciation_amount / days
depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
depreciation_amount -= depreciation_amount_for_current_month
date = monthly_schedule_date
amount = depreciation_amount_for_current_month
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / (month_range - 1)
elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(
month_range
) - 1:
# For last entry of monthly depr
date = last_schedule_date
amount = depreciation_amount / month_range
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / month_range
self.append(
"schedules",
{
"schedule_date": date,
"depreciation_amount": amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx,
},
)
else:
self.append(
"schedules",
{
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx,
},
)
self.append(
"schedules",
{
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx,
},
)
# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
# JE: Journal Entry, FB: Finance Book
@@ -853,7 +808,7 @@ class Asset(AccountsController):
depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
return 100 * (1 - flt(depreciation_rate, float_precision))
return flt((100 * (1 - depreciation_rate)), float_precision)
def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
days = date_diff(to_date, from_date)

View File

@@ -145,6 +145,7 @@ class TestAsset(AssetSetup):
def test_is_fixed_asset_set(self):
asset = create_asset(is_existing_asset=1)
doc = frappe.new_doc("Purchase Invoice")
doc.company = "_Test Company"
doc.supplier = "_Test Supplier"
doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
@@ -702,6 +703,8 @@ class TestDepreciationMethods(AssetSetup):
self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0)
def test_monthly_depreciation_by_wdv_method(self):
existing_precision = frappe.db.get_default("float_precision")
frappe.db.set_default("float_precision", 3)
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2022-02-15",
@@ -715,12 +718,12 @@ class TestDepreciationMethods(AssetSetup):
)
expected_schedules = [
["2022-02-28", 645.0, 645.0],
["2022-03-31", 1206.8, 1851.8],
["2022-04-30", 1051.12, 2902.92],
["2022-05-31", 915.52, 3818.44],
["2022-06-30", 797.42, 4615.86],
["2022-07-15", 384.14, 5000.0],
["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],
]
schedules = [
@@ -731,8 +734,8 @@ class TestDepreciationMethods(AssetSetup):
]
for d in asset.get("schedules")
]
self.assertEqual(schedules, expected_schedules)
frappe.db.set_default("float_precision", existing_precision)
class TestDepreciationBasics(AssetSetup):

View File

@@ -113,7 +113,7 @@ class RequestforQuotation(BuyingController):
def get_link(self):
# RFQ link for supplier portal
return get_url("/rfq/" + self.name)
return get_url("/app/request-for-quotation/" + self.name)
def update_supplier_part_no(self, supplier):
self.vendor = supplier
@@ -181,12 +181,20 @@ class RequestforQuotation(BuyingController):
doc_args = self.as_dict()
doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")})
# Get Contact Full Name
supplier_name = None
if data.get("contact"):
contact_name = frappe.db.get_value(
"Contact", data.get("contact"), ["first_name", "middle_name", "last_name"]
)
supplier_name = (" ").join(x for x in contact_name if x) # remove any blank values
args = {
"update_password_link": update_password_link,
"message": frappe.render_template(self.message_for_supplier, doc_args),
"rfq_link": rfq_link,
"user_fullname": full_name,
"supplier_name": data.get("supplier_name"),
"supplier_name": supplier_name or data.get("supplier_name"),
"supplier_salutation": self.salutation or "Dear Mx.",
}

View File

@@ -1470,8 +1470,15 @@ class AccountsController(TransactionBase):
self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to")
)
party_account_currency = get_account_currency(party_account)
allow_multi_currency_invoices_against_single_party_account = frappe.db.get_singles_value(
"Accounts Settings", "allow_multi_currency_invoices_against_single_party_account"
)
if not party_gle_currency and (party_account_currency != self.currency):
if (
not party_gle_currency
and (party_account_currency != self.currency)
and not allow_multi_currency_invoices_against_single_party_account
):
frappe.throw(
_("Party Account {0} currency ({1}) and document currency ({2}) should be same").format(
frappe.bold(party_account), party_account_currency, self.currency

View File

@@ -299,9 +299,10 @@ class BuyingController(StockController, Subcontracting):
raise_error_if_no_rate=False,
)
rate = flt(outgoing_rate * d.conversion_factor, d.precision("rate"))
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
else:
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate")
field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
if self.is_internal_transfer():
if rate != d.rate:

View File

@@ -18,8 +18,9 @@ from erpnext.stock.get_item_details import _get_item_tax_template
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def employee_query(doctype, txt, searchfield, start, page_len, filters):
doctype = "Employee"
conditions = []
fields = get_fields("Employee", ["name", "employee_name"])
fields = get_fields(doctype, ["name", "employee_name"])
return frappe.db.sql(
"""select {fields} from `tabEmployee`
@@ -49,7 +50,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def lead_query(doctype, txt, searchfield, start, page_len, filters):
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
doctype = "Lead"
fields = get_fields(doctype, ["name", "lead_name", "company_name"])
return frappe.db.sql(
"""select {fields} from `tabLead`
@@ -77,6 +79,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def customer_query(doctype, txt, searchfield, start, page_len, filters):
doctype = "Customer"
conditions = []
cust_master_name = frappe.defaults.get_user_default("cust_master_name")
@@ -85,9 +88,9 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
else:
fields = ["name", "customer_name", "customer_group", "territory"]
fields = get_fields("Customer", fields)
fields = get_fields(doctype, fields)
searchfields = frappe.get_meta("Customer").get_search_fields()
searchfields = frappe.get_meta(doctype).get_search_fields()
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
return frappe.db.sql(
@@ -116,6 +119,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
doctype = "Supplier"
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
if supp_master_name == "Supplier Name":
@@ -123,7 +127,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
else:
fields = ["name", "supplier_name", "supplier_group"]
fields = get_fields("Supplier", fields)
fields = get_fields(doctype, fields)
return frappe.db.sql(
"""select {field} from `tabSupplier`
@@ -147,6 +151,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
doctype = "Account"
company_currency = erpnext.get_company_currency(filters.get("company"))
def get_accounts(with_account_type_filter):
@@ -197,13 +202,14 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
doctype = "Item"
conditions = []
if isinstance(filters, str):
filters = json.loads(filters)
# Get searchfields from meta and use in Item Link field query
meta = frappe.get_meta("Item", cached=True)
meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields()
# these are handled separately
@@ -257,7 +263,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
filters.pop("supplier", None)
description_cond = ""
if frappe.db.count("Item", cache=True) < 50000:
if frappe.db.count(doctype, cache=True) < 50000:
# scan description only if items are less than 50000
description_cond = "or tabItem.description LIKE %(txt)s"
return frappe.db.sql(
@@ -300,8 +306,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def bom(doctype, txt, searchfield, start, page_len, filters):
doctype = "BOM"
conditions = []
fields = get_fields("BOM", ["name", "item"])
fields = get_fields(doctype, ["name", "item"])
return frappe.db.sql(
"""select {fields}
@@ -331,6 +338,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
doctype = "Project"
cond = ""
if filters and filters.get("customer"):
cond = """(`tabProject`.customer = %s or
@@ -338,9 +346,9 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
frappe.db.escape(filters.get("customer"))
)
fields = get_fields("Project", ["name", "project_name"])
searchfields = frappe.get_meta("Project").get_search_fields()
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
fields = get_fields(doctype, ["name", "project_name"])
searchfields = frappe.get_meta(doctype).get_search_fields()
searchfields = " or ".join(["`tabProject`." + field + " like %(txt)s" for field in searchfields])
return frappe.db.sql(
"""select {fields} from `tabProject`
@@ -366,7 +374,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
doctype = "Delivery Note"
fields = get_fields(doctype, ["name", "customer", "posting_date"])
return frappe.db.sql(
"""
@@ -402,6 +411,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
doctype = "Batch"
cond = ""
if filters.get("posting_date"):
cond = "and (batch.expiry_date is null or batch.expiry_date >= %(posting_date)s)"
@@ -420,7 +430,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
if filters.get("is_return"):
having_clause = ""
meta = frappe.get_meta("Batch", cached=True)
meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields()
search_columns = ""
@@ -496,6 +506,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
doctype = "Account"
filter_list = []
if isinstance(filters, dict):
@@ -514,7 +525,7 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
return frappe.desk.reportview.execute(
"Account",
doctype,
filters=filter_list,
fields=["name", "parent_account"],
limit_start=start,
@@ -553,6 +564,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
if not filters:
filters = {}
doctype = "Account"
condition = ""
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
@@ -628,6 +640,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
if not filters:
filters = {}
doctype = "Account"
condition = ""
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
@@ -650,6 +663,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
@frappe.validate_and_sanitize_search_inputs
def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
# Should be used when item code is passed in filters.
doctype = "Warehouse"
conditions, bin_conditions = [], []
filter_dict = get_doctype_wise_filters(filters)

View File

@@ -614,13 +614,13 @@ class SellingController(StockController):
stock_items = [d.item_code, d.description, d.warehouse, ""]
non_stock_items = [d.item_code, d.description]
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
duplicate_items_msg += "<br><br>"
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
get_link_to_form("Selling Settings", "Selling Settings"),
)
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
duplicate_items_msg += "<br><br>"
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
get_link_to_form("Selling Settings", "Selling Settings"),
)
if stock_items in check_list:
frappe.throw(duplicate_items_msg)
else:

View File

@@ -355,6 +355,8 @@ class Subcontracting:
rm_obj.purchase_order = item_row.purchase_order
self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
rm_obj.amount = flt(rm_obj.required_qty) * flt(rm_obj.rate)
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)

View File

@@ -48,7 +48,7 @@
"read_only": 1
},
{
"fetch_from": "website_item.image",
"fetch_from": "website_item.website_image",
"fieldname": "website_item_image",
"fieldtype": "Attach",
"label": "Website Item Image",
@@ -75,7 +75,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-07-13 21:02:19.031652",
"modified": "2022-06-28 16:44:24.718728",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "Recommended Items",
@@ -83,5 +83,6 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -30,10 +30,6 @@ frappe.ui.form.on('Website Item', {
}, __("View"));
},
image: () => {
refresh_field("image_view");
},
copy_from_item_group: (frm) => {
return frm.call({
doc: frm.doc,

View File

@@ -22,7 +22,6 @@
"column_break_11",
"description",
"brand",
"image",
"display_section",
"website_image",
"website_image_alt",
@@ -113,8 +112,11 @@
{
"description": "Item Image (if not slideshow)",
"fieldname": "website_image",
"fieldtype": "Attach",
"label": "Website Image"
"fieldtype": "Attach Image",
"hidden": 1,
"in_preview": 1,
"label": "Website Image",
"print_hide": 1
},
{
"description": "Image Alternative Text",
@@ -188,14 +190,6 @@
"options": "Item Group",
"read_only": 1
},
{
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"in_preview": 1,
"label": "Image",
"print_hide": 1
},
{
"default": "1",
"fieldname": "published",
@@ -348,13 +342,14 @@
}
],
"has_web_view": 1,
"image_field": "image",
"image_field": "website_image",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-09-02 13:08:41.942726",
"modified": "2022-06-28 17:10:30.613251",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "Website Item",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -410,6 +405,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "web_item_name",
"track_changes": 1
}

View File

@@ -1,7 +1,11 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
from typing import TYPE_CHECKING, List, Union
if TYPE_CHECKING:
from erpnext.stock.doctype.item.item import Item
import frappe
from frappe import _
@@ -116,11 +120,6 @@ class WebsiteItem(WebsiteGenerator):
if frappe.flags.in_import:
return
auto_set_website_image = False
if not self.website_image and self.image:
auto_set_website_image = True
self.website_image = self.image
if not self.website_image:
return
@@ -137,18 +136,16 @@ class WebsiteItem(WebsiteGenerator):
file_doc = file_doc[0]
if not file_doc:
if not auto_set_website_image:
frappe.msgprint(
_("Website Image {0} attached to Item {1} cannot be found").format(
self.website_image, self.name
)
frappe.msgprint(
_("Website Image {0} attached to Item {1} cannot be found").format(
self.website_image, self.name
)
)
self.website_image = None
elif file_doc.is_private:
if not auto_set_website_image:
frappe.msgprint(_("Website Image should be a public file or website URL"))
frappe.msgprint(_("Website Image should be a public file or website URL"))
self.website_image = None
@@ -159,9 +156,8 @@ class WebsiteItem(WebsiteGenerator):
import requests.exceptions
if not self.is_new() and self.website_image != frappe.db.get_value(
self.doctype, self.name, "website_image"
):
db_website_image = frappe.db.get_value(self.doctype, self.name, "website_image")
if not self.is_new() and self.website_image != db_website_image:
self.thumbnail = None
if self.website_image and not self.thumbnail:
@@ -437,7 +433,9 @@ def check_if_user_is_customer(user=None):
@frappe.whitelist()
def make_website_item(doc, save=True):
def make_website_item(doc: "Item", save: bool = True) -> Union["WebsiteItem", List[str]]:
"Make Website Item from Item. Used via Form UI or patch."
if not doc:
return
@@ -457,7 +455,6 @@ def make_website_item(doc, save=True):
"item_group",
"stock_uom",
"brand",
"image",
"has_variants",
"variant_of",
"description",
@@ -465,6 +462,10 @@ def make_website_item(doc, save=True):
for field in fields_to_map:
website_item.update({field: doc.get(field)})
# Needed for publishing/mapping via Form UI only
if not frappe.flags.in_migrate and (doc.get("image") and not website_item.website_image):
website_item.website_image = doc.get("image")
if not save:
return website_item

View File

@@ -1,5 +1,5 @@
frappe.listview_settings['Website Item'] = {
add_fields: ["item_name", "web_item_name", "published", "image", "has_variants", "variant_of"],
add_fields: ["item_name", "web_item_name", "published", "website_image", "has_variants", "variant_of"],
filters: [["published", "=", "1"]],
get_indicator: function(doc) {

View File

@@ -20,7 +20,15 @@ def add_to_wishlist(item_code):
web_item_data = frappe.db.get_value(
"Website Item",
{"item_code": item_code},
["image", "website_warehouse", "name", "web_item_name", "item_name", "item_group", "route"],
[
"website_image",
"website_warehouse",
"name",
"web_item_name",
"item_name",
"item_group",
"route",
],
as_dict=1,
)
@@ -30,7 +38,7 @@ def add_to_wishlist(item_code):
"item_group": web_item_data.get("item_group"),
"website_item": web_item_data.get("name"),
"web_item_name": web_item_data.get("web_item_name"),
"image": web_item_data.get("image"),
"image": web_item_data.get("website_image"),
"warehouse": web_item_data.get("website_warehouse"),
"route": web_item_data.get("route"),
}

View File

@@ -35,7 +35,6 @@ class ProductQuery:
"variant_of",
"has_variants",
"item_group",
"image",
"web_long_description",
"short_description",
"route",

View File

@@ -35,7 +35,7 @@ erpnext.ProductGrid = class {
}
get_image_html(item, title) {
let image = item.website_image || item.image;
let image = item.website_image;
if (image) {
return `

View File

@@ -35,7 +35,7 @@ erpnext.ProductList = class {
}
get_image_html(item, title, settings) {
let image = item.website_image || item.image;
let image = item.website_image;
let wishlist_enabled = !item.has_variants && settings.enable_wishlist;
let image_html = ``;

View File

@@ -199,16 +199,32 @@ def get_fee_components(fee_structure):
@frappe.whitelist()
def get_fee_schedule(program, student_category=None):
def get_fee_schedule(program, student_category=None, academic_year=None):
"""Returns Fee Schedule.
:param program: Program.
:param student_category: Student Category
:param student_category: Student Category.
:param academic_year: Academic Year.
"""
fs = frappe.get_all(
"Program Fee",
fields=["academic_term", "fee_structure", "due_date", "amount"],
filters={"parent": program, "student_category": student_category},
filters = {}
if program:
filters = {"program": program}
if student_category:
filters["student_category"] = student_category
if academic_year:
filters["academic_year"] = academic_year
fs = frappe.db.get_list(
"Fee Schedule",
filters=filters,
fields=[
"academic_term",
"fee_structure",
"student_category",
"due_date",
"total_amount as amount",
],
order_by="idx",
)
return fs

View File

@@ -60,12 +60,15 @@ frappe.ui.form.on('Program Enrollment', {
method: 'erpnext.education.api.get_fee_schedule',
args: {
'program': frm.doc.program,
'student_category': frm.doc.student_category
'student_category': frm.doc.student_category,
'academic_year': frm.doc.academic_year
},
callback: function(r) {
if (r.message) {
cur_frm.clear_table("fees");
frm.refresh_fields('fees');
frm.set_value('fees' ,r.message);
frm.events.get_courses(frm);
frm.refresh_fields('fees');
}
}
});
@@ -76,6 +79,10 @@ frappe.ui.form.on('Program Enrollment', {
frappe.ui.form.trigger('Program Enrollment', 'program');
},
academic_year: function() {
frappe.ui.form.trigger('Program Enrollment', 'program');
},
get_courses: function(frm) {
frm.set_value('courses',[]);
frappe.call({

View File

@@ -105,6 +105,8 @@ class ProgramEnrollment(Document):
"academic_term": d.academic_term,
"fee_structure": d.fee_structure,
"program": self.program,
"student_batch": self.student_batch_name,
"student_category": self.student_category,
"due_date": d.due_date,
"student_name": self.student_name,
"program_enrollment": self.name,

View File

@@ -483,12 +483,12 @@ scheduler_events = {
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
"erpnext.projects.doctype.project.project.hourly_reminder",
"erpnext.projects.doctype.project.project.collect_project_status",
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
],
"hourly_long": [
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
],
"daily": [
"erpnext.support.doctype.issue.issue.auto_close_tickets",
@@ -589,6 +589,7 @@ accounting_dimension_doctypes = [
"Shipping Rule",
"Landed Cost Item",
"Asset Value Adjustment",
"Asset Repair",
"Loyalty Program",
"Fee Schedule",
"Fee Structure",

View File

@@ -813,7 +813,7 @@
"idx": 24,
"image_field": "image",
"links": [],
"modified": "2021-06-17 11:31:37.730760",
"modified": "2022-07-18 20:03:43.188705",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",

View File

@@ -10,7 +10,7 @@ from frappe.permissions import (
remove_user_permission,
set_user_permission_if_allowed,
)
from frappe.utils import add_years, cstr, getdate, today, validate_email_address
from frappe.utils import add_days, add_years, cstr, getdate, today, validate_email_address
from frappe.utils.nestedset import NestedSet
from erpnext.utilities.transaction_base import delete_events
@@ -64,6 +64,8 @@ class Employee(NestedSet):
if existing_user_id:
remove_user_permission("Employee", self.name, existing_user_id)
self.update_to_date_in_work_history()
def after_rename(self, old, new, merge):
self.db_set("employee", new)
@@ -166,6 +168,18 @@ class Employee(NestedSet):
user.flags.ignore_permissions = True
user.add_roles("Expense Approver")
def update_to_date_in_work_history(self):
if not self.internal_work_history:
return
for idx, row in enumerate(self.internal_work_history):
if not row.from_date or idx == 0:
continue
self.internal_work_history[idx - 1].to_date = add_days(row.from_date, -1)
self.internal_work_history[-1].to_date = None
def validate_date(self):
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
throw(_("Date of Birth cannot be greater than today."))
@@ -349,7 +363,9 @@ def get_employee_email(employee_doc):
def get_holiday_list_for_employee(employee, raise_exception=True):
if employee:
holiday_list, company = frappe.db.get_value("Employee", employee, ["holiday_list", "company"])
holiday_list, company = frappe.get_cached_value(
"Employee", employee, ["holiday_list", "company"]
)
else:
holiday_list = ""
company = frappe.db.get_value("Global Defaults", None, "default_company")

View File

@@ -26,7 +26,8 @@
"fieldtype": "Link",
"label": "Employee",
"options": "Employee",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"fetch_from": "employee.employee_name",
@@ -48,7 +49,8 @@
"fieldtype": "Link",
"label": "Shift",
"options": "Shift Type",
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "column_break_4",
@@ -107,7 +109,7 @@
}
],
"links": [],
"modified": "2020-07-08 11:02:32.660986",
"modified": "2022-07-19 15:38:41.767539",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Checkin",

View File

@@ -131,7 +131,7 @@ def mark_attendance_and_link_log(
return None
elif attendance_status in ("Present", "Absent", "Half Day"):
employee_doc = frappe.get_doc("Employee", employee)
company = frappe.get_cached_value("Employee", employee, "company")
duplicate = frappe.db.exists(
"Attendance",
{"employee": employee, "attendance_date": attendance_date, "docstatus": ("!=", "2")},
@@ -144,7 +144,7 @@ def mark_attendance_and_link_log(
"attendance_date": attendance_date,
"status": attendance_status,
"working_hours": working_hours,
"company": employee_doc.company,
"company": company,
"shift": shift,
"late_entry": late_entry,
"early_exit": early_exit,

View File

@@ -80,12 +80,14 @@ class TestEmployeeTransfer(unittest.TestCase):
department = ["Accounts - TC", "Management - TC"]
designation = ["Accountant", "Manager"]
dt = [getdate("01-10-2021"), getdate()]
to_date = [add_days(dt[1], -1), None]
employee = frappe.get_doc("Employee", employee)
for data in employee.internal_work_history:
self.assertEqual(data.department, department[count])
self.assertEqual(data.designation, designation[count])
self.assertEqual(data.from_date, dt[count])
self.assertEqual(data.to_date, to_date[count])
count = count + 1
transfer.cancel()
@@ -95,6 +97,7 @@ class TestEmployeeTransfer(unittest.TestCase):
self.assertEqual(data.designation, designation[0])
self.assertEqual(data.department, department[0])
self.assertEqual(data.from_date, dt[0])
self.assertEqual(data.to_date, None)
def create_company():

View File

@@ -254,9 +254,11 @@ frappe.ui.form.on("Expense Claim", {
}, __("View"));
}
if (frm.doc.docstatus===1 && !cint(frm.doc.is_paid) && cint(frm.doc.grand_total) > 0
&& (cint(frm.doc.total_amount_reimbursed) < cint(frm.doc.total_sanctioned_amount))
&& frappe.model.can_create("Payment Entry")) {
if (
frm.doc.docstatus === 1
&& frm.doc.status !== "Paid"
&& frappe.model.can_create("Payment Entry")
) {
frm.add_custom_button(__('Payment'),
function() { frm.events.make_payment_entry(frm); }, __('Create'));
}

View File

@@ -305,12 +305,12 @@ class ExpenseClaim(AccountsController):
if self.total_advance_amount:
precision = self.precision("total_advance_amount")
if flt(self.total_advance_amount, precision) > flt(self.total_claimed_amount, precision):
frappe.throw(_("Total advance amount cannot be greater than total claimed amount"))
amount_with_taxes = flt(
(flt(self.total_sanctioned_amount, precision) + flt(self.total_taxes_and_charges, precision)),
precision,
)
if self.total_sanctioned_amount and flt(self.total_advance_amount, precision) > flt(
self.total_sanctioned_amount, precision
):
if flt(self.total_advance_amount, precision) > amount_with_taxes:
frappe.throw(_("Total advance amount cannot be greater than total sanctioned amount"))
def validate_sanctioned_amount(self):
@@ -339,6 +339,30 @@ def update_reimbursed_amount(doc, amount):
frappe.db.set_value("Expense Claim", doc.name, "status", doc.status)
def get_outstanding_amount_for_claim(claim):
if isinstance(claim, str):
claim = frappe.db.get_value(
"Expense Claim",
claim,
(
"total_sanctioned_amount",
"total_taxes_and_charges",
"total_amount_reimbursed",
"total_advance_amount",
),
as_dict=True,
)
outstanding_amt = (
flt(claim.total_sanctioned_amount)
+ flt(claim.total_taxes_and_charges)
- flt(claim.total_amount_reimbursed)
- flt(claim.total_advance_amount)
)
return outstanding_amt
@frappe.whitelist()
def make_bank_entry(dt, dn):
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
@@ -348,11 +372,7 @@ def make_bank_entry(dt, dn):
if not default_bank_cash_account:
default_bank_cash_account = get_default_bank_cash_account(expense_claim.company, "Cash")
payable_amount = (
flt(expense_claim.total_sanctioned_amount)
- flt(expense_claim.total_amount_reimbursed)
- flt(expense_claim.total_advance_amount)
)
payable_amount = get_outstanding_amount_for_claim(expense_claim)
je = frappe.new_doc("Journal Entry")
je.voucher_type = "Bank Entry"

View File

@@ -4,6 +4,7 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import flt, nowdate, random_string
from erpnext.accounts.doctype.account.test_account import create_account
@@ -14,9 +15,18 @@ test_dependencies = ["Employee"]
company_name = "_Test Company 3"
class TestExpenseClaim(unittest.TestCase):
def tearDown(self):
frappe.db.rollback()
class TestExpenseClaim(FrappeTestCase):
def setUp(self):
if not frappe.db.get_value("Cost Center", {"company": company_name}):
frappe.get_doc(
{
"doctype": "Cost Center",
"cost_center_name": "_Test Cost Center 3",
"parent_cost_center": "_Test Company 3 - _TC3",
"is_group": 0,
"company": company_name,
}
).insert()
def test_total_expense_claim_for_project(self):
frappe.db.sql("""delete from `tabTask`""")
@@ -58,12 +68,7 @@ class TestExpenseClaim(unittest.TestCase):
payable_account, 300, 200, company_name, "Travel Expenses - _TC3"
)
je_dict = make_bank_entry("Expense Claim", expense_claim.name)
je = frappe.get_doc(je_dict)
je.posting_date = nowdate()
je.cheque_no = random_string(5)
je.cheque_date = nowdate()
je.submit()
je = make_journal_entry(expense_claim)
expense_claim = frappe.get_doc("Expense Claim", expense_claim.name)
self.assertEqual(expense_claim.status, "Paid")
@@ -109,6 +114,40 @@ class TestExpenseClaim(unittest.TestCase):
self.assertEqual(claim.grand_total, 0)
self.assertEqual(claim.status, "Paid")
def test_advance_amount_allocation_against_claim_with_taxes(self):
from erpnext.hr.doctype.employee_advance.test_employee_advance import (
get_advances_for_claim,
make_employee_advance,
make_payment_entry,
)
frappe.db.delete("Employee Advance")
payable_account = get_payable_account("_Test Company")
taxes = generate_taxes("_Test Company")
claim = make_expense_claim(
payable_account,
700,
700,
"_Test Company",
"Travel Expenses - _TC",
do_not_submit=True,
taxes=taxes,
)
claim.save()
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
pe.submit()
# claim for already paid out advances
claim = get_advances_for_claim(claim, advance.name, 763)
claim.save()
claim.submit()
self.assertEqual(claim.grand_total, 0)
self.assertEqual(claim.status, "Paid")
def test_expense_claim_partially_paid_via_advance(self):
from erpnext.hr.doctype.employee_advance.test_employee_advance import (
get_advances_for_claim,
@@ -272,17 +311,36 @@ class TestExpenseClaim(unittest.TestCase):
self.assertEqual(outstanding_amount, 0)
self.assertEqual(total_amount_reimbursed, 5500)
def test_journal_entry_against_expense_claim(self):
payable_account = get_payable_account(company_name)
taxes = generate_taxes()
expense_claim = make_expense_claim(
payable_account,
300,
200,
company_name,
"Travel Expenses - _TC3",
do_not_submit=True,
taxes=taxes,
)
expense_claim.submit()
je = make_journal_entry(expense_claim)
self.assertEqual(je.accounts[0].debit_in_account_currency, expense_claim.grand_total)
def get_payable_account(company):
return frappe.get_cached_value("Company", company, "default_payable_account")
def generate_taxes():
def generate_taxes(company=None):
company = company or company_name
parent_account = frappe.db.get_value(
"Account", {"company": company_name, "is_group": 1, "account_type": "Tax"}, "name"
"Account", filters={"account_name": "Duties and Taxes", "company": company}
)
account = create_account(
company=company_name,
company=company,
account_name="Output Tax CGST",
account_type="Tax",
parent_account=parent_account,
@@ -370,3 +428,14 @@ def make_payment_entry(expense_claim, payable_account, amt):
pe.references[0].allocated_amount = amt
pe.insert()
pe.submit()
def make_journal_entry(expense_claim):
je_dict = make_bank_entry("Expense Claim", expense_claim.name)
je = frappe.get_doc(je_dict)
je.posting_date = nowdate()
je.cheque_no = random_string(5)
je.cheque_date = nowdate()
je.submit()
return je

View File

@@ -115,6 +115,8 @@ def is_holiday(holiday_list, date=None):
if date is None:
date = today()
if holiday_list:
return bool(frappe.get_all("Holiday List", dict(name=holiday_list, holiday_date=date)))
return bool(
frappe.db.exists("Holiday", {"parent": holiday_list, "holiday_date": date}, cache=True)
)
else:
return False

View File

@@ -25,7 +25,8 @@
"fieldtype": "Link",
"label": "Employee",
"options": "Employee",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"fetch_from": "employee.employee_name",
@@ -48,7 +49,8 @@
"in_list_view": 1,
"label": "Shift Type",
"options": "Shift Type",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"fieldname": "column_break_3",
@@ -105,7 +107,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-06-15 14:27:54.310773",
"modified": "2022-07-19 15:27:54.310773",
"modified_by": "Administrator",
"module": "HR",
"name": "Shift Assignment",

View File

@@ -169,7 +169,7 @@ def get_employee_shift(
"""
if for_date is None:
for_date = nowdate()
default_shift = frappe.db.get_value("Employee", employee, "default_shift")
default_shift = frappe.get_cached_value("Employee", employee, "default_shift")
shift_type_name = None
shift_assignment_details = frappe.db.get_value(
"Shift Assignment",
@@ -187,7 +187,7 @@ def get_employee_shift(
if not shift_type_name and consider_default_shift:
shift_type_name = default_shift
if shift_type_name:
holiday_list_name = frappe.db.get_value("Shift Type", shift_type_name, "holiday_list")
holiday_list_name = frappe.get_cached_value("Shift Type", shift_type_name, "holiday_list")
if not holiday_list_name:
holiday_list_name = get_holiday_list_for_employee(employee, False)
if holiday_list_name and is_holiday(holiday_list_name, for_date):
@@ -294,7 +294,18 @@ def get_shift_details(shift_type_name, for_date=None):
return None
if not for_date:
for_date = nowdate()
shift_type = frappe.get_doc("Shift Type", shift_type_name)
shift_type = frappe.get_cached_value(
"Shift Type",
shift_type_name,
[
"name",
"start_time",
"end_time",
"begin_check_in_before_shift_start_time",
"allow_check_out_after_shift_end_time",
],
as_dict=1,
)
start_datetime = datetime.combine(for_date, datetime.min.time()) + shift_type.start_time
for_date = (
for_date + timedelta(days=1) if shift_type.start_time > shift_type.end_time else for_date

View File

@@ -107,7 +107,7 @@ class ShiftType(Document):
"""Marks Absents for the given employee on working days in this shift which have no attendance marked.
The Absent is marked starting from 'process_attendance_after' or employee creation date.
"""
date_of_joining, relieving_date, employee_creation = frappe.db.get_value(
date_of_joining, relieving_date, employee_creation = frappe.get_cached_value(
"Employee", employee, ["date_of_joining", "relieving_date", "creation"]
)
if not date_of_joining:
@@ -156,21 +156,19 @@ class ShiftType(Document):
if not from_date:
del filters["start_date"]
assigned_employees = frappe.get_all("Shift Assignment", "employee", filters, as_list=True)
assigned_employees = [x[0] for x in assigned_employees]
assigned_employees = frappe.get_all("Shift Assignment", filters, pluck="employee")
if consider_default_shift:
filters = {"default_shift": self.name, "status": ["!=", "Inactive"]}
default_shift_employees = frappe.get_all("Employee", "name", filters, as_list=True)
default_shift_employees = [x[0] for x in default_shift_employees]
default_shift_employees = frappe.get_all("Employee", filters, pluck="name")
return list(set(assigned_employees + default_shift_employees))
return assigned_employees
def process_auto_attendance_for_all_shifts():
shift_list = frappe.get_all("Shift Type", "name", {"enable_auto_attendance": "1"}, as_list=True)
shift_list = frappe.get_all("Shift Type", filters={"enable_auto_attendance": "1"}, pluck="name")
for shift in shift_list:
doc = frappe.get_doc("Shift Type", shift[0])
doc = frappe.get_cached_doc("Shift Type", shift)
doc.process_auto_attendance()

View File

@@ -224,6 +224,7 @@ def delete_employee_work_history(details, employee, date):
filters["from_date"] = date
if filters:
frappe.db.delete("Employee Internal Work History", filters)
employee.reload()
@frappe.whitelist()

View File

@@ -48,6 +48,10 @@
"total_payment",
"total_principal_paid",
"written_off_amount",
"refund_amount",
"debit_adjustment_amount",
"credit_adjustment_amount",
"is_npa",
"column_break_19",
"total_interest_payable",
"total_amount_paid",
@@ -379,12 +383,39 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "refund_amount",
"fieldtype": "Currency",
"label": "Refund amount",
"no_copy": 1,
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "credit_adjustment_amount",
"fieldtype": "Currency",
"label": "Credit Adjustment Amount",
"options": "Company:company:default_currency"
},
{
"fieldname": "debit_adjustment_amount",
"fieldtype": "Currency",
"label": "Debit Adjustment Amount",
"options": "Company:company:default_currency"
},
{
"default": "0",
"description": "Mark Loan as a Nonperforming asset",
"fieldname": "is_npa",
"fieldtype": "Check",
"label": "Is NPA"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-03-10 11:50:31.957360",
"modified": "2022-06-30 12:04:13.728880",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Loan Balance Adjustment', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,189 @@
{
"actions": [],
"autoname": "LM-ADJ-.#####",
"creation": "2022-06-28 14:48:47.736269",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"loan",
"applicant_type",
"applicant",
"column_break_3",
"company",
"posting_date",
"accounting_dimensions_section",
"cost_center",
"section_break_9",
"adjustment_account",
"column_break_11",
"adjustment_type",
"amount",
"reference_number",
"remarks",
"amended_from"
],
"fields": [
{
"fieldname": "loan",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Loan",
"options": "Loan",
"reqd": 1
},
{
"fetch_from": "loan.applicant_type",
"fieldname": "applicant_type",
"fieldtype": "Select",
"label": "Applicant Type",
"options": "Employee\nMember\nCustomer",
"read_only": 1
},
{
"fetch_from": "loan.applicant",
"fieldname": "applicant",
"fieldtype": "Dynamic Link",
"label": "Applicant ",
"options": "applicant_type",
"read_only": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fetch_from": "loan.company",
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"read_only": 1,
"reqd": 1
},
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Posting Date",
"reqd": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"label": "Adjustment Details"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "reference_number",
"fieldtype": "Data",
"label": "Reference Number"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Loan Balance Adjustment",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Loan Balance Adjustment",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "adjustment_account",
"fieldtype": "Link",
"label": "Adjustment Account",
"options": "Account",
"reqd": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
"options": "Company:company:default_currency",
"reqd": 1
},
{
"fieldname": "adjustment_type",
"fieldtype": "Select",
"label": "Adjustment Type",
"options": "Credit Adjustment\nDebit Adjustment",
"reqd": 1
},
{
"fieldname": "remarks",
"fieldtype": "Data",
"label": "Remarks"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-07-08 16:48:54.480066",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Balance Adjustment",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Loan Manager",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -0,0 +1,143 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.utils import add_days, nowdate
import erpnext
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import (
process_loan_interest_accrual_for_demand_loans,
)
class LoanBalanceAdjustment(AccountsController):
"""
Add credit/debit adjustments to loan ledger.
"""
def validate(self):
if self.amount == 0:
frappe.throw(_("Amount cannot be zero"))
if self.amount < 0:
frappe.throw(_("Amount cannot be negative"))
self.set_missing_values()
def on_submit(self):
self.set_status_and_amounts()
self.make_gl_entries()
def on_cancel(self):
self.set_status_and_amounts(cancel=1)
self.make_gl_entries(cancel=1)
self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
def set_missing_values(self):
if not self.posting_date:
self.posting_date = nowdate()
if not self.cost_center:
self.cost_center = erpnext.get_default_cost_center(self.company)
def set_status_and_amounts(self, cancel=0):
loan_details = frappe.db.get_value(
"Loan",
self.loan,
[
"loan_amount",
"credit_adjustment_amount",
"debit_adjustment_amount",
"total_payment",
"total_principal_paid",
"total_interest_payable",
"status",
"is_term_loan",
"is_secured_loan",
],
as_dict=1,
)
if cancel:
adjustment_amount = self.get_values_on_cancel(loan_details)
else:
adjustment_amount = self.get_values_on_submit(loan_details)
if self.adjustment_type == "Credit Adjustment":
adj_field = "credit_adjustment_amount"
elif self.adjustment_type == "Debit Adjustment":
adj_field = "debit_adjustment_amount"
frappe.db.set_value("Loan", self.loan, {adj_field: adjustment_amount})
def get_values_on_cancel(self, loan_details):
if self.adjustment_type == "Credit Adjustment":
adjustment_amount = loan_details.credit_adjustment_amount - self.amount
elif self.adjustment_type == "Debit Adjustment":
adjustment_amount = loan_details.debit_adjustment_amount - self.amount
return adjustment_amount
def get_values_on_submit(self, loan_details):
if self.adjustment_type == "Credit Adjustment":
adjustment_amount = loan_details.credit_adjustment_amount + self.amount
elif self.adjustment_type == "Debit Adjustment":
adjustment_amount = loan_details.debit_adjustment_amount + self.amount
if loan_details.status in ("Disbursed", "Partially Disbursed") and not loan_details.is_term_loan:
process_loan_interest_accrual_for_demand_loans(
posting_date=add_days(self.posting_date, -1),
loan=self.loan,
accrual_type=self.adjustment_type,
)
return adjustment_amount
def make_gl_entries(self, cancel=0, adv_adj=0):
gle_map = []
loan_account = frappe.db.get_value("Loan", self.loan, "loan_account")
remarks = "{} against loan {}".format(self.adjustment_type.capitalize(), self.loan)
if self.reference_number:
remarks += " with reference no. {}".format(self.reference_number)
loan_entry = {
"account": loan_account,
"against": self.adjustment_account,
"against_voucher_type": "Loan",
"against_voucher": self.loan,
"remarks": _(remarks),
"cost_center": self.cost_center,
"party_type": self.applicant_type,
"party": self.applicant,
"posting_date": self.posting_date,
}
company_entry = {
"account": self.adjustment_account,
"against": loan_account,
"against_voucher_type": "Loan",
"against_voucher": self.loan,
"remarks": _(remarks),
"cost_center": self.cost_center,
"posting_date": self.posting_date,
}
if self.adjustment_type == "Credit Adjustment":
loan_entry["credit"] = self.amount
loan_entry["credit_in_account_currency"] = self.amount
company_entry["debit"] = self.amount
company_entry["debit_in_account_currency"] = self.amount
elif self.adjustment_type == "Debit Adjustment":
loan_entry["debit"] = self.amount
loan_entry["debit_in_account_currency"] = self.amount
company_entry["credit"] = self.amount
company_entry["credit_in_account_currency"] = self.amount
gle_map.append(self.get_gl_dict(loan_entry))
gle_map.append(self.get_gl_dict(company_entry))
if gle_map:
make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestLoanBalanceAdjustment(FrappeTestCase):
pass

View File

@@ -163,11 +163,11 @@
},
{
"fetch_from": "against_loan.disbursement_account",
"fetch_if_empty": 1,
"fieldname": "disbursement_account",
"fieldtype": "Link",
"label": "Disbursement Account",
"options": "Account",
"read_only": 1
"options": "Account"
},
{
"fieldname": "column_break_16",
@@ -185,7 +185,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-02-17 18:23:44.157598",
"modified": "2022-08-04 17:16:04.922444",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Disbursement",

View File

@@ -209,6 +209,9 @@ def get_disbursal_amount(loan, on_current_security_price=0):
"loan_amount",
"disbursed_amount",
"total_payment",
"debit_adjustment_amount",
"credit_adjustment_amount",
"refund_amount",
"total_principal_paid",
"total_interest_payable",
"status",

View File

@@ -35,12 +35,15 @@
{
"fieldname": "loan",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Loan",
"options": "Loan"
},
{
"fieldname": "posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Posting Date"
},
{
@@ -75,6 +78,8 @@
"fetch_from": "loan.applicant",
"fieldname": "applicant",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Applicant",
"options": "applicant_type"
},
@@ -158,8 +163,11 @@
{
"fieldname": "accrual_type",
"fieldtype": "Select",
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Accrual Type",
"options": "Regular\nRepayment\nDisbursement"
"options": "Regular\nRepayment\nDisbursement\nCredit Adjustment\nDebit Adjustment\nRefund"
},
{
"fieldname": "penalty_amount",
@@ -185,10 +193,11 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-04-19 18:26:38.871889",
"modified": "2022-06-30 11:51:31.911794",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Interest Accrual",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -225,5 +234,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -147,6 +147,9 @@ def make_accrual_interest_entry_for_demand_loans(
"name",
"total_payment",
"total_amount_paid",
"debit_adjustment_amount",
"credit_adjustment_amount",
"refund_amount",
"loan_account",
"interest_income_account",
"loan_amount",

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Loan Refund', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,176 @@
{
"actions": [],
"autoname": "LM-RF-.#####",
"creation": "2022-06-24 15:51:03.165498",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"loan",
"applicant_type",
"applicant",
"column_break_3",
"company",
"posting_date",
"accounting_dimensions_section",
"cost_center",
"section_break_9",
"refund_account",
"column_break_11",
"refund_amount",
"reference_number",
"amended_from"
],
"fields": [
{
"fieldname": "loan",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Loan",
"options": "Loan",
"reqd": 1
},
{
"fetch_from": "loan.applicant_type",
"fieldname": "applicant_type",
"fieldtype": "Select",
"label": "Applicant Type",
"options": "Employee\nMember\nCustomer",
"read_only": 1
},
{
"fetch_from": "loan.applicant",
"fieldname": "applicant",
"fieldtype": "Dynamic Link",
"label": "Applicant ",
"options": "applicant_type",
"read_only": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fetch_from": "loan.company",
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"read_only": 1,
"reqd": 1
},
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Posting Date",
"reqd": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"label": "Refund Details"
},
{
"fieldname": "refund_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Refund Account",
"options": "Account",
"reqd": 1
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "refund_amount",
"fieldtype": "Currency",
"label": "Refund Amount",
"options": "Company:company:default_currency",
"reqd": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Loan Refund",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Loan Refund",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "reference_number",
"fieldtype": "Data",
"label": "Reference Number"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-06-24 16:13:48.793486",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Refund",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Loan Manager",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -0,0 +1,97 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.utils import getdate
import erpnext
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
get_pending_principal_amount,
)
class LoanRefund(AccountsController):
"""
Add refund if total repayment is more than that is owed.
"""
def validate(self):
self.set_missing_values()
self.validate_refund_amount()
def set_missing_values(self):
if not self.cost_center:
self.cost_center = erpnext.get_default_cost_center(self.company)
def validate_refund_amount(self):
loan = frappe.get_doc("Loan", self.loan)
pending_amount = get_pending_principal_amount(loan)
if pending_amount >= 0:
frappe.throw(_("No excess amount to refund."))
else:
excess_amount = pending_amount * -1
if self.refund_amount > excess_amount:
frappe.throw(_("Refund amount cannot be greater than excess amount {0}").format(excess_amount))
def on_submit(self):
self.update_outstanding_amount()
self.make_gl_entries()
def on_cancel(self):
self.update_outstanding_amount(cancel=1)
self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
self.make_gl_entries(cancel=1)
def update_outstanding_amount(self, cancel=0):
refund_amount = frappe.db.get_value("Loan", self.loan, "refund_amount")
if cancel:
refund_amount -= self.refund_amount
else:
refund_amount += self.refund_amount
frappe.db.set_value("Loan", self.loan, "refund_amount", refund_amount)
def make_gl_entries(self, cancel=0):
gl_entries = []
loan_details = frappe.get_doc("Loan", self.loan)
gl_entries.append(
self.get_gl_dict(
{
"account": self.refund_account,
"against": loan_details.loan_account,
"credit": self.refund_amount,
"credit_in_account_currency": self.refund_amount,
"against_voucher_type": "Loan",
"against_voucher": self.loan,
"remarks": _("Against Loan:") + self.loan,
"cost_center": self.cost_center,
"posting_date": getdate(self.posting_date),
}
)
)
gl_entries.append(
self.get_gl_dict(
{
"account": loan_details.loan_account,
"party_type": loan_details.applicant_type,
"party": loan_details.applicant,
"against": self.refund_account,
"debit": self.refund_amount,
"debit_in_account_currency": self.refund_amount,
"against_voucher_type": "Loan",
"against_voucher": self.loan,
"remarks": _("Against Loan:") + self.loan,
"cost_center": self.cost_center,
"posting_date": getdate(self.posting_date),
}
)
)
make_gl_entries(gl_entries, cancel=cancel, merge_entries=False)

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestLoanRefund(FrappeTestCase):
pass

View File

@@ -281,11 +281,11 @@
},
{
"fetch_from": "against_loan.payment_account",
"fetch_if_empty": 1,
"fieldname": "payment_account",
"fieldtype": "Link",
"label": "Repayment Account",
"options": "Account",
"read_only": 1
"options": "Account"
},
{
"fieldname": "column_break_36",
@@ -311,7 +311,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-02-18 19:10:07.742298",
"modified": "2022-08-04 17:13:51.964203",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Repayment",
@@ -353,4 +353,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -150,6 +150,9 @@ class LoanRepayment(AccountsController):
"status",
"is_secured_loan",
"total_payment",
"debit_adjustment_amount",
"credit_adjustment_amount",
"refund_amount",
"loan_amount",
"disbursed_amount",
"total_interest_payable",
@@ -387,15 +390,19 @@ class LoanRepayment(AccountsController):
def make_gl_entries(self, cancel=0, adv_adj=0):
gle_map = []
if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(
remarks = "Shortfall repayment of {0}.<br>Repayment against loan {1}".format(
self.shortfall_amount, self.against_loan
)
elif self.shortfall_amount:
remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount)
remarks = "Shortfall repayment of {0} against loan {1}".format(
self.shortfall_amount, self.against_loan
)
else:
remarks = _("Repayment against Loan: ") + self.against_loan
remarks = "Repayment against loan " + self.against_loan
if self.reference_number:
remarks += " with reference no. {}".format(self.reference_number)
if self.repay_from_salary:
payment_account = self.payroll_payable_account
@@ -446,7 +453,7 @@ class LoanRepayment(AccountsController):
"debit_in_account_currency": self.amount_paid,
"against_voucher_type": "Loan",
"against_voucher": self.against_loan,
"remarks": remarks,
"remarks": _(remarks),
"cost_center": self.cost_center,
"posting_date": getdate(self.posting_date),
}
@@ -464,7 +471,7 @@ class LoanRepayment(AccountsController):
"credit_in_account_currency": self.amount_paid,
"against_voucher_type": "Loan",
"against_voucher": self.against_loan,
"remarks": remarks,
"remarks": _(remarks),
"cost_center": self.cost_center,
"posting_date": getdate(self.posting_date),
}
@@ -624,16 +631,22 @@ def get_pending_principal_amount(loan):
if loan.status in ("Disbursed", "Closed") or loan.disbursed_amount >= loan.loan_amount:
pending_principal_amount = (
flt(loan.total_payment)
+ flt(loan.debit_adjustment_amount)
- flt(loan.credit_adjustment_amount)
- flt(loan.total_principal_paid)
- flt(loan.total_interest_payable)
- flt(loan.written_off_amount)
+ flt(loan.refund_amount)
)
else:
pending_principal_amount = (
flt(loan.disbursed_amount)
+ flt(loan.debit_adjustment_amount)
- flt(loan.credit_adjustment_amount)
- flt(loan.total_principal_paid)
- flt(loan.total_interest_payable)
- flt(loan.written_off_amount)
+ flt(loan.refund_amount)
)
return pending_principal_amount

View File

@@ -7,6 +7,8 @@
"engine": "InnoDB",
"field_order": [
"loan",
"applicant_type",
"applicant",
"status",
"column_break_3",
"shortfall_time",
@@ -23,6 +25,8 @@
{
"fieldname": "loan",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Loan ",
"options": "Loan",
"read_only": 1
@@ -91,17 +95,35 @@
{
"fieldname": "shortfall_percentage",
"fieldtype": "Percent",
"in_list_view": 1,
"label": "Shortfall Percentage",
"read_only": 1
},
{
"fetch_from": "loan.applicant_type",
"fieldname": "applicant_type",
"fieldtype": "Select",
"label": "Applicant Type",
"options": "Employee\nMember\nCustomer"
},
{
"fetch_from": "loan.applicant",
"fieldname": "applicant",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Applicant",
"options": "applicant_type"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-04-01 08:13:43.263772",
"modified": "2022-06-30 11:57:09.378089",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security Shortfall",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -132,5 +154,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -58,6 +58,9 @@ class LoanSecurityUnpledge(Document):
self.loan,
[
"total_payment",
"debit_adjustment_amount",
"credit_adjustment_amount",
"refund_amount",
"total_principal_paid",
"loan_amount",
"total_interest_payable",

View File

@@ -9,6 +9,9 @@ from frappe.utils import cint, flt, getdate
import erpnext
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
get_pending_principal_amount,
)
class LoanWriteOff(AccountsController):
@@ -22,16 +25,26 @@ class LoanWriteOff(AccountsController):
def validate_write_off_amount(self):
precision = cint(frappe.db.get_default("currency_precision")) or 2
total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value(
loan_details = frappe.get_value(
"Loan",
self.loan,
["total_payment", "total_principal_paid", "total_interest_payable", "written_off_amount"],
[
"total_payment",
"debit_adjustment_amount",
"credit_adjustment_amount",
"refund_amount",
"total_principal_paid",
"loan_amount",
"total_interest_payable",
"written_off_amount",
"disbursed_amount",
"status",
],
as_dict=1,
)
pending_principal_amount = flt(
flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount),
precision,
)
pending_principal_amount = flt(get_pending_principal_amount(loan_details), precision)
if self.write_off_amount > pending_principal_amount:
frappe.throw(_("Write off amount cannot be greater than pending principal amount"))

View File

@@ -54,17 +54,18 @@
"fieldtype": "Select",
"hidden": 1,
"label": "Accrual Type",
"options": "Regular\nRepayment\nDisbursement",
"options": "Regular\nRepayment\nDisbursement\nCredit Adjustment\nDebit Adjustment\nRefund",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-11-06 13:28:51.478909",
"modified": "2022-06-29 11:19:33.203088",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Process Loan Interest Accrual",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -98,5 +99,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -444,6 +444,7 @@ class BOM(WebsiteGenerator):
and self.is_active
):
frappe.db.set(self, "is_default", 1)
frappe.db.set_value("Item", self.item, "default_bom", self.name)
else:
frappe.db.set(self, "is_default", 0)
item = frappe.get_doc("Item", self.item)

View File

@@ -519,6 +519,42 @@ class TestBOM(FrappeTestCase):
new_bom.delete()
def test_set_default_bom_for_item_having_single_bom(self):
from erpnext.stock.doctype.item.test_item import make_item
fg_item = make_item(properties={"is_stock_item": 1})
bom_item = make_item(properties={"is_stock_item": 1})
# Step 1: Create BOM
bom = frappe.new_doc("BOM")
bom.item = fg_item.item_code
bom.quantity = 1
bom.append(
"items",
{
"item_code": bom_item.item_code,
"qty": 1,
"uom": bom_item.stock_uom,
"stock_uom": bom_item.stock_uom,
"rate": 100.0,
},
)
bom.save()
bom.submit()
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
# Step 2: Uncheck is_active field
bom.is_active = 0
bom.save()
bom.reload()
self.assertIsNone(frappe.get_value("Item", fg_item.item_code, "default_bom"))
# Step 3: Check is_active field
bom.is_active = 1
bom.save()
bom.reload()
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@@ -21,13 +21,18 @@ frappe.ui.form.on('Member', {
// custom buttons
frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.set_route('query-report', 'General Ledger',
{party_type:'Member', party:frm.doc.name});
if (frm.doc.customer) {
frappe.set_route('query-report', 'General Ledger', {party_type: 'Customer', party: frm.doc.customer});
} else {
frappe.set_route('query-report', 'General Ledger', {party_type: 'Member', party: frm.doc.name});
}
});
frm.add_custom_button(__('Accounts Receivable'), function() {
frappe.set_route('query-report', 'Accounts Receivable', {member:frm.doc.name});
});
if (frm.doc.customer) {
frm.add_custom_button(__('Accounts Receivable'), function() {
frappe.set_route('query-report', 'Accounts Receivable', {customer: frm.doc.customer});
});
}
if (!frm.doc.customer) {
frm.add_custom_button(__('Create Customer'), () => {

View File

@@ -368,3 +368,8 @@ erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
erpnext.patches.v13_0.update_employee_advance_status
erpnext.patches.v13_0.job_card_status_on_hold
erpnext.patches.v13_0.add_cost_center_in_loans
erpnext.patches.v13_0.show_india_localisation_deprecation_warning
erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
erpnext.patches.v13_0.reset_corrupt_defaults
erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair

View File

@@ -0,0 +1,29 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
accounting_dimensions = frappe.db.get_all(
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
)
if not accounting_dimensions:
return
for d in accounting_dimensions:
doctype = "Asset Repair"
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": "accounting_dimensions_section",
}
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)

View File

@@ -33,7 +33,10 @@ def execute():
"insert_after": insert_after_field,
}
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)
try:
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)
except Exception as e:
pass
count += 1

View File

@@ -17,7 +17,6 @@ def execute():
"item_group",
"stock_uom",
"brand",
"image",
"has_variants",
"variant_of",
"description",
@@ -30,6 +29,7 @@ def execute():
"website_warehouse",
"web_long_description",
"website_content",
"website_image",
"thumbnail",
]

View File

@@ -0,0 +1,16 @@
import frappe
def execute():
if not frappe.db.has_column("Asset", "allow_monthly_depreciation"):
return
assets = frappe.get_all("Asset", filters={"allow_monthly_depreciation": 1})
for d in assets:
print(d.name)
asset_doc = frappe.get_doc("Asset", d.name)
for i in asset_doc.get("finance_books"):
if i.frequency_of_depreciation != 1:
i.total_number_of_depreciations *= i.frequency_of_depreciation
i.frequency_of_depreciation = 1
i.db_update()

View File

@@ -0,0 +1,29 @@
import frappe
def execute():
"""
This patch is needed to fix parent incorrectly set as `__2fa` because of
https://github.com/frappe/frappe/commit/a822092211533ff17ff9b92dd86f6f868ed63e2e
"""
for doctype in (
"Accounts Settings",
"Stock Settings",
"Selling Settings",
"Buying Settings",
"CRM Settings",
"Global Defaults",
"Healthcare Settings",
"Education Settings",
):
try:
frappe.get_single(doctype).save()
except Exception:
pass
try:
pos_profile = frappe.get_last_doc("POS Profile")
pos_profile.set_defaults()
except Exception:
pass

View File

@@ -0,0 +1,16 @@
import click
import frappe
def execute():
if "hrms" in frappe.get_installed_apps():
return
click.secho(
"HR and Payroll modules have been moved to a separate app"
" and will be removed from ERPNext in Version 14."
" Please install the HRMS app when upgrading to Version 14"
" to continue using the HR and Payroll modules:\n"
"https://github.com/frappe/hrms",
fg="yellow",
)

View File

@@ -0,0 +1,15 @@
import click
import frappe
def execute():
if not frappe.db.exists("Company", {"country": "India"}):
return
click.secho(
"India-specific regional features have been moved to a separate app"
" and will be removed from ERPNext in Version 14."
" Please install India Compliance after upgrading to Version 14:\n"
"https://github.com/resilient-tech/india-compliance",
fg="yellow",
)

View File

@@ -622,11 +622,23 @@ class SalarySlip(TransactionBase):
self.add_tax_components(payroll_period)
def add_structure_components(self, component_type):
data = self.get_data_for_eval()
data, default_data = self.get_data_for_eval()
timesheet_component = frappe.db.get_value(
"Salary Structure", self.salary_structure, "salary_component"
)
for struct_row in self._salary_structure_doc.get(component_type):
if self.salary_slip_based_on_timesheet and struct_row.salary_component == timesheet_component:
continue
amount = self.eval_condition_and_formula(struct_row, data)
if amount is not None and struct_row.statistical_component == 0:
self.update_component_row(struct_row, amount, component_type)
if (
amount or (struct_row.amount_based_on_formula and amount is not None)
) and struct_row.statistical_component == 0:
default_amount = self.eval_condition_and_formula(struct_row, default_data)
self.update_component_row(
struct_row, amount, component_type, data=data, default_amount=default_amount
)
def get_data_for_eval(self):
"""Returns data for evaluating formula"""
@@ -670,11 +682,15 @@ class SalarySlip(TransactionBase):
for sc in salary_components:
data.setdefault(sc.salary_component_abbr, 0)
# shallow copy of data to store default amounts (without payment days) for tax calculation
default_data = data.copy()
for key in ("earnings", "deductions"):
for d in self.get(key):
default_data[d.abbr] = d.default_amount
data[d.abbr] = d.amount
return data
return data, default_data
def eval_condition_and_formula(self, d, data):
try:
@@ -780,7 +796,14 @@ class SalarySlip(TransactionBase):
self.update_component_row(tax_row, tax_amount, "deductions")
def update_component_row(
self, component_data, amount, component_type, additional_salary=None, is_recurring=0
self,
component_data,
amount,
component_type,
additional_salary=None,
is_recurring=0,
data=None,
default_amount=None,
):
component_row = None
for d in self.get(component_type):
@@ -841,7 +864,7 @@ class SalarySlip(TransactionBase):
additional_salary.deduct_full_tax_on_selected_payroll_date
)
else:
component_row.default_amount = amount
component_row.default_amount = default_amount or amount
component_row.additional_amount = 0
component_row.deduct_full_tax_on_selected_payroll_date = (
component_data.deduct_full_tax_on_selected_payroll_date
@@ -850,6 +873,8 @@ class SalarySlip(TransactionBase):
component_row.amount = amount
self.update_component_amount_based_on_payment_days(component_row)
if data:
data[component_row.abbr] = component_row.amount
def update_component_amount_based_on_payment_days(self, component_row):
joining_date, relieving_date = self.get_joining_and_relieving_dates()
@@ -1272,7 +1297,7 @@ class SalarySlip(TransactionBase):
)[0].total_amount
def calculate_tax_by_tax_slab(self, annual_taxable_earning, tax_slab):
data = self.get_data_for_eval()
data, default_data = self.get_data_for_eval()
data.update({"annual_taxable_earning": annual_taxable_earning})
tax_amount = 0
for slab in tax_slab.slabs:
@@ -1350,23 +1375,22 @@ class SalarySlip(TransactionBase):
self.total_interest_amount = 0
self.total_principal_amount = 0
if not self.get("loans"):
for loan in self.get_loan_details():
self.set("loans", [])
for loan in self.get_loan_details():
amounts = calculate_amounts(loan.name, self.posting_date, "Regular Payment")
amounts = calculate_amounts(loan.name, self.posting_date, "Regular Payment")
if amounts["interest_amount"] or amounts["payable_principal_amount"]:
self.append(
"loans",
{
"loan": loan.name,
"total_payment": amounts["interest_amount"] + amounts["payable_principal_amount"],
"interest_amount": amounts["interest_amount"],
"principal_amount": amounts["payable_principal_amount"],
"loan_account": loan.loan_account,
"interest_income_account": loan.interest_income_account,
},
)
if amounts["interest_amount"] or amounts["payable_principal_amount"]:
self.append(
"loans",
{
"loan": loan.name,
"total_payment": amounts["interest_amount"] + amounts["payable_principal_amount"],
"interest_amount": amounts["interest_amount"],
"principal_amount": amounts["payable_principal_amount"],
"loan_account": loan.loan_account,
"interest_income_account": loan.interest_income_account,
},
)
for payment in self.get("loans"):
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")

View File

@@ -7,7 +7,7 @@ import unittest
import frappe
from frappe.model.document import Document
from frappe.tests.utils import change_settings
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import (
add_days,
add_months,
@@ -35,13 +35,12 @@ from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_detail
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
class TestSalarySlip(unittest.TestCase):
class TestSalarySlip(FrappeTestCase):
def setUp(self):
setup_test()
frappe.flags.pop("via_payroll_entry", None)
def tearDown(self):
frappe.db.rollback()
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
frappe.set_user("Administrator")
@@ -372,13 +371,19 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(salary_slip.payment_days, days_in_month - no_of_holidays - 1)
# gross pay calculation based on attendance (payment days)
gross_pay = 78100 - (
(78000 / (days_in_month - no_of_holidays))
* flt(salary_slip.leave_without_pay + salary_slip.absent_days)
# component calculation based on attendance (payment days)
amount, precision = None, None
for row in salary_slip.earnings:
if row.salary_component == "Basic Salary":
amount = row.amount
precision = row.precision("amount")
break
expected_amount = flt(
(50000 * salary_slip.payment_days / salary_slip.total_working_days), precision
)
self.assertEqual(salary_slip.gross_pay, flt(gross_pay, 2))
self.assertEqual(amount, expected_amount)
@change_settings("Payroll Settings", {"payroll_based_on": "Attendance"})
def test_component_amount_dependent_on_another_payment_days_based_component(self):
@@ -919,6 +924,41 @@ class TestSalarySlip(unittest.TestCase):
# undelete fixture data
frappe.db.rollback()
@change_settings(
"Payroll Settings",
{
"payroll_based_on": "Attendance",
"consider_unmarked_attendance_as": "Present",
"include_holidays_in_total_working_days": True,
},
)
def test_default_amount(self):
# Special Allowance (SA) uses another component Basic (BS) in it's formula : BD * .5
# Basic has "Depends on Payment Days" enabled
# Test default amount for SA is based on default amount for BS (irrespective of PD)
# Test amount for SA is based on amount for BS (based on PD)
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
month_start_date = get_first_day(nowdate())
joining_date = add_days(month_start_date, 3)
employee = make_employee("test_tax_for_mid_joinee@salary.com", date_of_joining=joining_date)
salary_structure = make_salary_structure(
"Stucture to test tax",
"Monthly",
test_tax=True,
from_date=joining_date,
employee=employee,
)
ss = make_salary_slip(salary_structure.name, employee=employee)
# default amount for SA (special allowance = BS*0.5) should be based on default amount for basic
self.assertEqual(ss.earnings[2].default_amount, 25000)
self.assertEqual(
ss.earnings[2].amount, flt(ss.earnings[0].amount * 0.5, ss.earnings[0].precision("amount"))
)
def test_tax_for_recurring_additional_salary(self):
frappe.db.sql("""delete from `tabPayroll Period`""")
frappe.db.sql("""delete from `tabSalary Component`""")
@@ -981,6 +1021,16 @@ class TestSalarySlip(unittest.TestCase):
frappe.db.rollback()
def test_do_not_show_statistical_component_in_slip(self):
make_employee("test_statistical_component@salary.com")
new_ss = make_employee_salary_slip(
"test_statistical_component@salary.com",
"Monthly",
"Test Payment Based On Attendence",
)
components = [row.salary_component for row in new_ss.get("earnings")]
self.assertNotIn("Statistical Component", components)
def make_activity_for_employee(self):
activity_type = frappe.get_doc("Activity Type", "_Test Activity Type")
activity_type.billing_rate = 50
@@ -1038,7 +1088,7 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None, po
def make_salary_component(salary_components, test_tax, company_list=None):
for salary_component in salary_components:
if frappe.db.exists("Salary Component", salary_component["salary_component"]):
continue
frappe.delete_doc("Salary Component", salary_component["salary_component"], force=True)
if test_tax:
if salary_component["type"] == "Earning":
@@ -1119,8 +1169,16 @@ def make_earning_salary_component(
"formula": "BS*.5",
"type": "Earning",
"amount_based_on_formula": 1,
"depends_on_payment_days": 0,
},
{"salary_component": "Leave Encashment", "abbr": "LE", "type": "Earning"},
{
"salary_component": "Statistical Component",
"abbr": "SC",
"type": "Earning",
"statistical_component": 1,
"amount": 500,
},
]
if include_flexi_benefits:
data.extend(
@@ -1418,6 +1476,10 @@ def setup_test():
"Salary Slip",
"Attendance",
"Additional Salary",
"Employee Tax Exemption Declaration",
"Employee Tax Exemption Proof Submission",
"Employee Benefit Claim",
"Salary Structure Assignment",
]:
frappe.db.sql("delete from `tab%s`" % dt)

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import re
import frappe
from frappe import _
@@ -18,6 +19,8 @@ class SalaryStructure(Document):
self.strip_condition_and_formula_fields()
self.validate_max_benefits_with_flexi()
self.validate_component_based_on_tax_slab()
self.validate_payment_days_based_dependent_component()
self.validate_timesheet_component()
def set_missing_values(self):
overwritten_fields = [
@@ -58,6 +61,50 @@ class SalaryStructure(Document):
if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet:
frappe.throw(_("Net pay cannot be negative"))
def validate_payment_days_based_dependent_component(self):
abbreviations = self.get_component_abbreviations()
for component_type in ("earnings", "deductions"):
for row in self.get(component_type):
if (
row.formula
and row.depends_on_payment_days
# check if the formula contains any of the payment days components
and any(re.search(r"\b" + abbr + r"\b", row.formula) for abbr in abbreviations)
):
message = _("Row #{0}: The {1} Component has the options {2} and {3} enabled.").format(
row.idx,
frappe.bold(row.salary_component),
frappe.bold("Amount based on formula"),
frappe.bold("Depends On Payment Days"),
)
message += "<br><br>" + _(
"Disable {0} for the {1} component, to prevent the amount from being deducted twice, as its formula already uses a payment-days-based component."
).format(
frappe.bold("Depends On Payment Days"), frappe.bold(row.salary_component)
)
frappe.throw(message, title=_("Payment Days Dependency"))
def get_component_abbreviations(self):
abbr = [d.abbr for d in self.earnings if d.depends_on_payment_days]
abbr += [d.abbr for d in self.deductions if d.depends_on_payment_days]
return abbr
def validate_timesheet_component(self):
if not self.salary_slip_based_on_timesheet:
return
for component in self.earnings:
if component.salary_component == self.salary_component:
frappe.msgprint(
_(
"Row #{0}: Timesheet amount will overwrite the Earning component amount for the Salary Component {1}"
).format(self.idx, frappe.bold(self.salary_component)),
title=_("Warning"),
indicator="orange",
)
break
def strip_condition_and_formula_fields(self):
# remove whitespaces from condition and formula fields
for row in self.earnings:

View File

@@ -150,6 +150,7 @@ def make_salary_structure(
currency=erpnext.get_default_currency(),
payroll_period=None,
include_flexi_benefits=False,
base=None,
):
if test_tax:
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""", (salary_structure))
@@ -200,6 +201,7 @@ def make_salary_structure(
company=company,
currency=currency,
payroll_period=payroll_period,
base=base,
)
return salary_structure_doc

View File

@@ -16,7 +16,7 @@ class Homepage(Document):
def setup_items(self):
for d in frappe.get_all(
"Website Item",
fields=["name", "item_name", "description", "image", "route"],
fields=["name", "item_name", "description", "website_image", "route"],
filters={"published": 1},
limit=3,
):
@@ -31,7 +31,7 @@ class Homepage(Document):
item_code=d.name,
item_name=d.item_name,
description=d.description,
image=d.image,
image=d.website_image,
route=d.route,
),
)

View File

@@ -2122,7 +2122,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
"qty": item.qty,
"description": item.description,
"serial_no": item.serial_no,
"batch_no": item.batch_no
"batch_no": item.batch_no,
"sample_size": item.sample_quantity
});
dialog_items.grid.refresh();
}

View File

@@ -9,6 +9,9 @@
"section_break_2",
"sandbox_mode",
"applicable_from",
"column_break_4",
"dont_show_discounts_in_e_invoice",
"section_break_7",
"credentials",
"advanced_settings_section",
"client_id",
@@ -80,12 +83,28 @@
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"default": "1",
"description": "Enabling this will directly report net rates in e-Invoice post discounts",
"fieldname": "dont_show_discounts_in_e_invoice",
"fieldtype": "Check",
"label": "Don't show discounts in e-Invoice"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hide_border": 1
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-11-16 19:50:28.029517",
"modified": "2022-07-17 14:57:50.783517",
"modified_by": "Administrator",
"module": "Regional",
"name": "E Invoice Settings",

View File

@@ -144,7 +144,7 @@ class GSTR3BReport(Document):
def get_inward_nil_exempt(self, state):
inward_nil_exempt = frappe.db.sql(
"""
SELECT p.place_of_supply, p.supplier_address,
SELECT p.name, p.place_of_supply, p.supplier_address, p.gst_category,
i.base_amount, i.is_nil_exempt, i.is_non_gst
FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
WHERE p.docstatus = 1 and p.name = i.parent

View File

@@ -65,6 +65,7 @@ def validate_eligibility(doc):
"SEZ",
"Overseas",
"Deemed Export",
"UIN Holders",
]
company_transaction = doc.get("billing_address_gstin") == doc.get("company_gstin")
@@ -130,9 +131,7 @@ def read_json(name):
def get_transaction_details(invoice):
supply_type = ""
if (
invoice.gst_category == "Registered Regular" or invoice.gst_category == "Registered Composition"
):
if invoice.gst_category in ("Registered Regular", "Registered Composition", "UIN Holders"):
supply_type = "B2B"
elif invoice.gst_category == "SEZ":
if invoice.export_type == "Without Payment of Tax":
@@ -148,15 +147,18 @@ def get_transaction_details(invoice):
supply_type = "DEXP"
if not supply_type:
rr, rc, sez, overseas, export = (
rr, rc, sez, overseas, export, uin = (
bold("Registered Regular"),
bold("Registered Composition"),
bold("SEZ"),
bold("Overseas"),
bold("Deemed Export"),
bold("UIN Holders"),
)
frappe.throw(
_("GST category should be one of {}, {}, {}, {}, {}").format(rr, rc, sez, overseas, export),
_("GST category should be one of {}, {}, {}, {}, {}, {}").format(
rr, rc, sez, overseas, export, uin
),
title=_("Invalid Supply Type"),
)
@@ -263,6 +265,10 @@ def get_overseas_address_details(address_name):
def get_item_list(invoice):
item_list = []
hide_discount_in_einvoice = cint(
frappe.db.get_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice")
)
for d in invoice.items:
einvoice_item_schema = read_json("einv_item_template")
item = frappe._dict({})
@@ -272,14 +278,38 @@ def get_item_list(invoice):
item.description = sanitize_for_json(d.item_name)
item.qty = abs(item.qty)
item_qty = item.qty
if invoice.get("apply_discount_on"):
item.discount_amount = item.base_amount - item.base_net_amount
item.unit_rate = abs(item.taxable_value - item.discount_amount) / item.qty
item.gross_amount = abs(item.taxable_value) + item.discount_amount
item.taxable_value = abs(item.taxable_value)
if invoice.get("is_return") or invoice.get("is_debit_note"):
item_qty = item_qty or 1
if hide_discount_in_einvoice or invoice.is_internal_customer or item.discount_amount < 0:
item.unit_rate = item.taxable_value / item_qty
item.gross_amount = item.taxable_value
item.discount_amount = 0
else:
if invoice.get("apply_discount_on") and (abs(invoice.get("base_discount_amount") or 0.0) > 0.0):
# TODO: need to handle case when tax included in basic rate is checked.
item.discount_amount = (item.discount_amount * item_qty) + (
abs(item.base_amount) - abs(item.base_net_amount)
)
else:
item.discount_amount = item.discount_amount * item_qty
try:
item.unit_rate = (item.taxable_value + item.discount_amount) / item_qty
except ZeroDivisionError:
# This will never run but added as safety measure
frappe.throw(
title=_("Error: Qty is Zero"),
msg=_("Quantity can't be zero unless it's Credit/Debit Note."),
)
item.gross_amount = item.taxable_value + item.discount_amount
item.taxable_value = item.taxable_value
item.is_service_item = "Y" if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else "N"
item.serial_no = ""

View File

@@ -714,7 +714,7 @@ def get_custom_fields():
insert_after="customer",
no_copy=1,
print_hide=1,
depends_on='eval:in_list(["Registered Regular", "Registered Composition", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0',
depends_on='eval:in_list(["Registered Regular", "Registered Composition", "SEZ", "Overseas", "Deemed Export", "UIN Holders"], doc.gst_category) && doc.irn_cancelled === 0',
),
dict(
fieldname="irn_cancelled",

View File

@@ -40,13 +40,12 @@ def validate_gstin_for_india(doc, method):
gst_category = []
if hasattr(doc, "gst_category"):
if len(doc.links):
link_doctype = doc.links[0].get("link_doctype")
link_name = doc.links[0].get("link_name")
if len(doc.links):
link_doctype = doc.links[0].get("link_doctype")
link_name = doc.links[0].get("link_name")
if link_doctype in ["Customer", "Supplier"]:
gst_category = frappe.db.get_value(link_doctype, {"name": link_name}, ["gst_category"])
if link_doctype in ["Customer", "Supplier"]:
gst_category = frappe.db.get_value(link_doctype, {"name": link_name}, ["gst_category"])
doc.gstin = doc.gstin.upper().strip()
if not doc.gstin or doc.gstin == "NA":
@@ -581,7 +580,7 @@ def get_ewb_data(dt, dn):
if dt == "Delivery Note":
data.subSupplyType = 1
elif doc.gst_category in ["Registered Regular", "SEZ"]:
elif doc.gst_category in ["Unregistered", "Registered Regular", "SEZ"]:
data.subSupplyType = 1
elif doc.gst_category in ["Overseas", "Deemed Export"]:
data.subSupplyType = 3

View File

@@ -23,24 +23,31 @@ def _execute(filters=None):
if not filters:
filters = {}
columns = get_columns()
output_gst_accounts = get_output_gst_accounts(filters.company)
company_currency = erpnext.get_company_currency(filters.company)
item_list = get_items(filters)
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
itemised_tax, tax_columns = get_tax_accounts(
item_list, columns, company_currency, output_gst_accounts
)
data = []
added_item = []
for d in item_list:
if (d.parent, d.item_code) not in added_item:
row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty, d.tax_rate]
row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty]
total_tax = 0
tax_rate = 0
for tax in tax_columns:
item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
if item_tax.get("is_gst_tax"):
tax_rate += flt(item_tax.get("tax_rate", 0))
total_tax += flt(item_tax.get("tax_amount", 0))
row += [d.base_net_amount + total_tax]
row += [d.base_net_amount]
row += [tax_rate]
row += [d.taxable_value + total_tax]
row += [d.taxable_value]
for tax in tax_columns:
item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
row += [item_tax.get("tax_amount", 0)]
@@ -51,6 +58,40 @@ def _execute(filters=None):
return columns, data
def get_output_gst_accounts(company):
accounts = frappe.qb.DocType("Account")
gst_accounts = frappe.qb.DocType("GST Account")
accounts_query = (
frappe.qb.from_(accounts)
.select(accounts.name)
.where((accounts.account_type == "Tax") & (accounts.root_type == "Liability"))
)
gst_accounts_query = (
frappe.qb.from_(gst_accounts)
.select(
gst_accounts.cgst_account,
gst_accounts.sgst_account,
gst_accounts.igst_account,
gst_accounts.utgst_account,
gst_accounts.cess_account,
)
.where((gst_accounts.is_reverse_charge_account == 0) & (gst_accounts.company == company))
)
gst_accounts_list = [
account for sublist in gst_accounts_query.run() for account in sublist if account
]
tax_accounts_list = [account[0] for account in accounts_query.run() if account]
output_tax_list = [account for account in gst_accounts_list if account in tax_accounts_list]
return output_tax_list
def get_columns():
columns = [
{
@@ -99,34 +140,35 @@ def get_items(filters):
match_conditions = " and {0} ".format(match_conditions)
items = frappe.db.sql(
"""
select
f"""
SELECT
`tabSales Invoice Item`.gst_hsn_code,
`tabSales Invoice Item`.stock_uom,
sum(`tabSales Invoice Item`.stock_qty) as stock_qty,
sum(`tabSales Invoice Item`.base_net_amount) as base_net_amount,
sum(`tabSales Invoice Item`.base_price_list_rate) as base_price_list_rate,
sum(`tabSales Invoice Item`.stock_qty) AS stock_qty,
sum(`tabSales Invoice Item`.taxable_value) AS taxable_value,
sum(`tabSales Invoice Item`.base_price_list_rate) AS base_price_list_rate,
`tabSales Invoice Item`.parent,
`tabSales Invoice Item`.item_code,
`tabGST HSN Code`.description,
json_extract(`tabSales Taxes and Charges`.item_wise_tax_detail,
concat('$."' , `tabSales Invoice Item`.item_code, '"[0]')) * count(distinct `tabSales Taxes and Charges`.name) as tax_rate
from
`tabSales Invoice`,
`tabSales Invoice Item`,
`tabGST HSN Code`,
`tabSales Taxes and Charges`
where
`tabSales Invoice`.name = `tabSales Invoice Item`.parent
and `tabSales Taxes and Charges`.parent = `tabSales Invoice`.name
and `tabSales Invoice`.docstatus = 1
and `tabSales Invoice Item`.gst_hsn_code is not NULL
and `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name %s %s
group by
`tabGST HSN Code`.description
FROM
`tabSales Invoice`
INNER JOIN `tabSales Invoice Item` ON `tabSales Invoice`.name = `tabSales Invoice Item`.parent
INNER JOIN `tabGST HSN Code` ON `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name
WHERE
`tabSales Invoice`.docstatus = 1
AND `tabSales Invoice Item`.gst_hsn_code IS NOT NULL
{conditions}
GROUP BY
`tabSales Invoice Item`.parent,
`tabSales Invoice Item`.item_code
"""
% (conditions, match_conditions),
`tabSales Invoice Item`.item_code,
`tabSales Invoice Item`.gst_hsn_code,
`tabSales Invoice Item`.uom
ORDER BY
`tabSales Invoice Item`.gst_hsn_code,
`tabSales Invoice Item`.uom
""".format(
conditions=conditions
),
filters,
as_dict=1,
)
@@ -138,6 +180,7 @@ def get_tax_accounts(
item_list,
columns,
company_currency,
output_gst_accounts,
doctype="Sales Invoice",
tax_doctype="Sales Taxes and Charges",
):
@@ -177,7 +220,7 @@ def get_tax_accounts(
for parent, account_head, item_wise_tax_detail, tax_amount in tax_details:
if account_head not in tax_columns and tax_amount:
if account_head in output_gst_accounts and account_head not in tax_columns and tax_amount:
# as description is text editor earlier and markup can break the column convention in reports
tax_columns.append(account_head)
@@ -190,29 +233,40 @@ def get_tax_accounts(
continue
itemised_tax.setdefault(item_code, frappe._dict())
if isinstance(tax_data, list):
tax_rate = 0
is_gst_tax = 0
if account_head in output_gst_accounts:
is_gst_tax = 1
tax_rate = tax_data[0]
tax_amount = tax_data[1]
else:
tax_rate = 0
tax_amount = 0
for d in item_row_map.get(parent, {}).get(item_code, []):
item_tax_amount = tax_amount
if item_tax_amount:
itemised_tax.setdefault((parent, item_code), {})[account_head] = frappe._dict(
{"tax_amount": flt(item_tax_amount, tax_amount_precision)}
{
"tax_rate": flt(tax_rate, 2),
"is_gst_tax": is_gst_tax,
"tax_amount": flt(item_tax_amount, tax_amount_precision),
}
)
except ValueError:
continue
tax_columns.sort()
for account_head in tax_columns:
columns.append(
{
"label": account_head,
"fieldname": frappe.scrub(account_head),
"fieldtype": "Float",
"width": 110,
}
)
if account_head in output_gst_accounts:
columns.append(
{
"label": account_head,
"fieldname": frappe.scrub(account_head),
"fieldtype": "Float",
"width": 110,
}
)
return itemised_tax, tax_columns

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