Compare commits

...

87 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
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
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
Dany Robert
01d6df45d0 fix(india): e-way bill json for unregistered gst category 2022-07-15 11:17:41 +05:30
73 changed files with 11288 additions and 7068 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.36.1"
__version__ = "13.36.3"
def get_default_company(user=None):

View File

@@ -305,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":

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

@@ -2712,6 +2712,19 @@ class TestSalesInvoice(unittest.TestCase):
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
@@ -2804,6 +2817,19 @@ class TestSalesInvoice(unittest.TestCase):
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")

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

@@ -26,7 +26,6 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
supplier_map = get_supplier_pan_map()
tax_rate_map = get_tax_rate_map(filters)
gle_map = get_gle_map(tds_docs)
print(journal_entry_party_map)
out = []
for name, details in gle_map.items():

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

View File

@@ -301,7 +301,8 @@ class BuyingController(StockController, Subcontracting):
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

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

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

@@ -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."))

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

View File

@@ -114,6 +114,40 @@ class TestExpenseClaim(FrappeTestCase):
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,
@@ -300,12 +334,13 @@ 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,

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

@@ -99,7 +99,7 @@ class LoanBalanceAdjustment(AccountsController):
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)
remarks += " with reference no. {}".format(self.reference_number)
loan_entry = {
"account": loan_account,

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

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

@@ -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",
@@ -399,7 +402,7 @@ class LoanRepayment(AccountsController):
remarks = "Repayment against loan " + self.against_loan
if self.reference_number:
remarks += "with reference no. {}".format(self.reference_number)
remarks += " with reference no. {}".format(self.reference_number)
if self.repay_from_salary:
payment_account = self.payroll_payable_account

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

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

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

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

@@ -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, data=data)
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, data=None
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
@@ -1274,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:
@@ -1352,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":
@@ -1122,6 +1172,13 @@ def make_earning_salary_component(
"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(
@@ -1419,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

@@ -20,6 +20,7 @@ class SalaryStructure(Document):
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 = [
@@ -89,6 +90,21 @@ class SalaryStructure(Document):
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

@@ -265,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({})
@@ -276,17 +280,12 @@ def get_item_list(invoice):
item.qty = abs(item.qty)
item_qty = item.qty
item.discount_amount = abs(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
hide_discount_in_einvoice = cint(
frappe.db.get_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice")
)
if hide_discount_in_einvoice:
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

View File

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

View File

@@ -1548,6 +1548,65 @@ class TestSalesOrder(FrappeTestCase):
so.load_from_db()
self.assertEqual(so.billing_status, "Fully Billed")
def test_so_billing_status_with_crnote_against_sales_return(self):
"""
| Step | Document creation | |
|------+--------------------------------------+-------------------------------|
| 1 | SO -> DN -> SI | SO Fully Billed and Completed |
| 2 | DN -> Sales Return(Partial) | SO 50% Delivered, 100% billed |
| 3 | Sales Return(Partial) -> Credit Note | SO 50% Delivered, 50% billed |
"""
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
so = make_sales_order(uom="Nos", do_not_save=1)
so.save()
so.submit()
self.assertEqual(so.billing_status, "Not Billed")
dn1 = make_delivery_note(so.name)
dn1.taxes_and_charges = ""
dn1.taxes.clear()
dn1.save().submit()
si = create_sales_invoice(qty=10, do_not_save=1)
si.items[0].sales_order = so.name
si.items[0].so_detail = so.items[0].name
si.items[0].delivery_note = dn1.name
si.items[0].dn_detail = dn1.items[0].name
si.save()
si.submit()
so.reload()
self.assertEqual(so.billing_status, "Fully Billed")
self.assertEqual(so.status, "Completed")
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
dn1.reload()
dn_ret = create_delivery_note(is_return=1, return_against=dn1.name, qty=-5, do_not_submit=True)
dn_ret.items[0].against_sales_order = so.name
dn_ret.items[0].so_detail = so.items[0].name
dn_ret.submit()
so.reload()
self.assertEqual(so.per_billed, 100)
self.assertEqual(so.per_delivered, 50)
cr_note = create_sales_invoice(is_return=1, qty=-1, do_not_submit=True)
cr_note.items[0].qty = -5
cr_note.items[0].sales_order = so.name
cr_note.items[0].so_detail = so.items[0].name
cr_note.items[0].delivery_note = dn_ret.name
cr_note.items[0].dn_detail = dn_ret.items[0].name
cr_note.update_billed_amount_in_sales_order = True
cr_note.submit()
so.reload()
self.assertEqual(so.per_billed, 50)
self.assertEqual(so.per_delivered, 50)
def test_so_back_updated_from_wo_via_mr(self):
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
from erpnext.manufacturing.doctype.work_order.work_order import (

View File

@@ -497,7 +497,10 @@ erpnext.PointOfSale.Controller = class {
set_pos_profile_data() {
if (this.company && !this.frm.doc.company) this.frm.doc.company = this.company;
if (this.pos_profile && !this.frm.doc.pos_profile) this.frm.doc.pos_profile = this.pos_profile;
if ((this.pos_profile && !this.frm.doc.pos_profile) | (this.frm.doc.is_return && this.pos_profile != this.frm.doc.pos_profile)) {
this.frm.doc.pos_profile = this.pos_profile;
}
if (!this.frm.doc.company) return;
return this.frm.trigger("set_pos_data");

View File

@@ -161,13 +161,14 @@ erpnext.PointOfSale.Payment = class {
frappe.ui.form.on('POS Invoice', 'contact_mobile', (frm) => {
const contact = frm.doc.contact_mobile;
if (!this.request_for_payment_field) return;
const request_button = $(this.request_for_payment_field.$input[0]);
if (contact) {
request_button.removeClass('btn-default').addClass('btn-primary');
} else {
request_button.removeClass('btn-primary').addClass('btn-default');
}
});
}
});
frappe.ui.form.on('POS Invoice', 'coupon_code', (frm) => {
if (frm.doc.coupon_code && !frm.applying_pos_coupon_code) {

View File

@@ -143,10 +143,6 @@ def get_item_for_list_in_html(context):
if (context.get("website_image") or "").startswith("files/"):
context["website_image"] = "/" + quote(context["website_image"])
context["show_availability_status"] = cint(
frappe.db.get_single_value("E Commerce Settings", "show_availability_status")
)
products_template = "templates/includes/products_as_list.html"
return frappe.get_template(products_template).render(context)

View File

@@ -6,8 +6,6 @@ import frappe
from frappe import _
from frappe.utils.dashboard import cache_source
from erpnext.stock.utils import get_stock_value_from_bin
@frappe.whitelist()
@cache_source
@@ -30,26 +28,24 @@ def get(
warehouse_filters.append(["company", "=", filters.get("company")])
warehouses = frappe.get_list(
"Warehouse", fields=["name"], filters=warehouse_filters, order_by="name"
"Warehouse", pluck="name", filters=warehouse_filters, order_by="name"
)
for wh in warehouses:
balance = get_stock_value_from_bin(warehouse=wh.name)
wh["balance"] = balance[0][0]
warehouses = [x for x in warehouses if not (x.get("balance") == None)]
warehouses = frappe.get_list(
"Bin",
fields=["warehouse", "sum(stock_value) stock_value"],
filters={"warehouse": ["IN", warehouses], "stock_value": [">", 0]},
group_by="warehouse",
order_by="stock_value DESC",
limit_page_length=10,
)
if not warehouses:
return []
sorted_warehouse_map = sorted(warehouses, key=lambda i: i["balance"], reverse=True)
if len(sorted_warehouse_map) > 10:
sorted_warehouse_map = sorted_warehouse_map[:10]
for warehouse in sorted_warehouse_map:
labels.append(_(warehouse.get("name")))
datapoints.append(warehouse.get("balance"))
for warehouse in warehouses:
labels.append(_(warehouse.get("warehouse")))
datapoints.append(warehouse.get("stock_value"))
return {
"labels": labels,

View File

@@ -10,6 +10,7 @@
"company",
"purpose",
"customer",
"customer_name",
"work_order",
"material_request",
"for_qty",
@@ -126,11 +127,19 @@
"fieldtype": "Check",
"label": "Group Same Items",
"print_hide": 1
},
{
"depends_on": "eval:doc.purpose==='Delivery' && doc.customer",
"fetch_from": "customer.customer_name",
"fieldname": "customer_name",
"fieldtype": "Data",
"label": "Customer Name",
"read_only": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2022-04-21 07:56:40.646473",
"modified": "2022-07-19 11:03:04.442174",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
@@ -202,4 +211,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -635,6 +635,24 @@ class update_entries_after(object):
voucher_detail_no=sle.voucher_detail_no,
sle=sle,
)
elif (
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
and sle.actual_qty > 0
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
):
sle_details = frappe.db.get_value(
"Stock Ledger Entry",
{
"voucher_type": sle.voucher_type,
"voucher_no": sle.voucher_no,
"dependant_sle_voucher_detail_no": sle.voucher_detail_no,
},
["stock_value_difference", "actual_qty"],
as_dict=1,
)
rate = abs(sle_details.stock_value_difference / sle.actual_qty)
else:
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
rate_field = "valuation_rate"

View File

@@ -24,7 +24,7 @@
})
</script>
{% else %}
{{ product_image(doc.website_image or doc.image, alt=doc.website_image_alt or doc.item_name) }}
{{ product_image(doc.website_image, alt=doc.website_image_alt or doc.item_name) }}
{% endif %}
<!-- Simple image preview -->

View File

@@ -74,7 +74,7 @@
{%- set col_size = 3 if is_full_width else 4 -%}
{%- set title = item.web_item_name or item.item_name or item.item_code -%}
{%- set title = title[:50] + "..." if title|len > 50 else title -%}
{%- set image = item.website_image or item.image -%}
{%- set image = item.website_image -%}
{%- set description = item.website_description or item.description-%}
{% if is_featured %}

View File

@@ -1,7 +1,6 @@
/* csslint ignore:start */
{% if homepage.hero_image %}
.hero-image {
background-image: url("{{ homepage.hero_image }}");
background-size: cover;
padding: 10rem 0;
}

View File

@@ -5,7 +5,11 @@
{% block content %}
<main>
{% if homepage.hero_section_based_on == 'Default' %}
<section class="hero-section border-bottom {%if homepage.hero_image%}hero-image{%endif%}">
<section class="hero-section border-bottom {%if homepage.hero_image%}hero-image{%endif%}"
{% if homepage.hero_image %}
style="background-image: url('{{ homepage.hero_image }}');"
{%- endif %}
>
<div class="container py-5">
<h1 class="d-none d-sm-block display-4">{{ homepage.tag_line }}</h1>
<h1 class="d-block d-sm-none">{{ homepage.tag_line }}</h1>

View File

@@ -5997,7 +5997,7 @@ CN,CN,
DE,DE,
ES,ES,
FR,FR,
IN,IM,
IN,Ein,
JP,JP,
IT,ES,
MX,MX,
Can't render this file because it is too large.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff