Compare commits

..

315 Commits

Author SHA1 Message Date
Saqib
3a3a0a83b7 fix: link to error log list 2021-11-03 17:47:01 +05:30
Rohit Waghchaure
b8d75ff241 Merge branch 'version-13-pre-release' into version-13 2021-10-20 21:30:20 +05:30
Rohit Waghchaure
a3b7682935 bumped to version 13.13.0 2021-10-20 21:50:20 +05:50
rohitwaghchaure
200f6da8b2 Merge pull request #28040 from rohitwaghchaure/change-log-for-v13-13
chore: change log for v13.13.0
2021-10-20 21:23:39 +05:30
Rohit Waghchaure
7c9018f401 chore: change log for v13.13.0 2021-10-20 21:15:13 +05:30
Sagar Vora
ffadd671b7 fix: add mistakenly removed patches 2021-10-20 19:26:15 +05:30
mergify[bot]
47befa697d fix: incorrect status being set in Invoices (backport #28019) (#28030)
* fix: incorrect status being set in Invoices (#28019)

Co-authored-by: Pruthvi Patel <pruthvipatel145@gmail.com>
(cherry picked from commit 8d9d0987fe)

* fix: merge conflict

Co-authored-by: Sagar Vora <sagar@resilient.tech>
2021-10-20 19:24:19 +05:30
Jannat Patel
676c5280cc Merge pull request #28016 from frappe/mergify/bp/version-13-pre-release/pr-27728 2021-10-20 15:53:48 +05:30
Marica
e0decb0ae2 Merge pull request #28021 from frappe/mergify/bp/version-13-pre-release/pr-28005
fix: Fetch thumbnail from Item master instead of regenerating (backport #28005)
2021-10-20 15:14:30 +05:30
marination
b906cc20ae fix: Move thumbnail updation to different patch
- Thumbnail updation handled via different patch
- create_website_items will only have one purpose
- added progress bar to `create_website_items`
- code cleanup

(cherry picked from commit 348a961b53)
2021-10-20 09:12:33 +00:00
marination
c98421c69a fix: Check if thumbnail column exists in case of table trimming
(cherry picked from commit ac8014e24c)
2021-10-20 09:12:32 +00:00
marination
d7afb9ef65 fix: Get db values as dict when checking for thumbnail in existing web item
(cherry picked from commit 46a5a83789)
2021-10-20 09:12:32 +00:00
marination
a915b9cf72 fix: re-run patch
- Patch will just fetch thumbnails if website items are created, else it will create new website items

(cherry picked from commit 11c498d9e5)
2021-10-20 09:12:32 +00:00
marination
a022e01d3f fix: Fetch thumbnail from Item master instead of regenerating
(cherry picked from commit 94177c0764)
2021-10-20 09:12:31 +00:00
Goh Yan Chang
867cfa04b2 Update employee_leave_balance.py
fix: Employee Leave Balance report showing wrong figures
(cherry picked from commit 632f7848a3)
2021-10-20 06:58:12 +00:00
Jannat Patel
6e63dc1360 Merge pull request #28013 from frappe/mergify/bp/version-13-pre-release/pr-27904 2021-10-20 12:24:37 +05:30
Jannat Patel
698214bd59 fix: removed unused lines 2021-10-20 11:36:00 +05:30
Jannat Patel
873d166a4e fix: conflicts 2021-10-20 11:32:35 +05:30
Jannat Patel
9166d58717 fix: map missing fields in opportunity (#27904)
(cherry picked from commit d81f811349)

# Conflicts:
#	erpnext/crm/doctype/opportunity/opportunity.py
2021-10-20 05:56:59 +00:00
Deepesh Garg
7895d2a048 Merge pull request #28004 from frappe/mergify/bp/version-13-pre-release/pr-27867
fix: Totals row incorrect value in GL Entry (backport #27867)
2021-10-19 17:50:20 +05:30
Deepesh Garg
5a06ee9230 fix: Totals row incorrect value in GL Entry (#27867)
(cherry picked from commit ebe68c1a7a)
2021-10-19 09:39:02 +00:00
Noah Jacob
b6609d1649 Merge pull request #27998 from frappe/mergify/bp/version-13-pre-release/pr-27990
fix: changes in schedules gets overwritten on save (backport #27990)
2021-10-19 14:20:44 +05:30
Noah Jacob
9431bb9466 fix: changes in schedules gets overwritten on save
(cherry picked from commit af1b9e100e)
2021-10-19 08:07:42 +00:00
mergify[bot]
fdd9cc76be fix: flaky Org Chart Test (#27971) (#27989)
(cherry picked from commit 8eacaddde7)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-10-18 11:47:22 +05:30
Deepesh Garg
f8348ab681 Merge pull request #27983 from frappe/mergify/bp/version-13-pre-release/pr-27967
fix: Account number and name incorrectly imported using COA importer (backport #27967)
2021-10-18 11:04:39 +05:30
Deepesh Garg
eecfb25c90 Merge pull request #27981 from frappe/mergify/bp/version-13-pre-release/pr-27934
fix: TDS round off not working from second transaction (backport #27934)
2021-10-18 11:03:00 +05:30
Deepesh Garg
41a0e12954 Merge pull request #27979 from frappe/mergify/bp/version-13-pre-release/pr-27970
fix (India): Interstate internal transfer invoices not visible in GSTR-1 (backport #27970)
2021-10-18 11:01:06 +05:30
Ankush Menat
f0383289d8 Merge pull request #27986 from frappe/mergify/bp/version-13-pre-release/pr-27962
fix: Retain space inside Serial no string while cleaning serial nos (backport #27962)
2021-10-18 10:34:31 +05:30
Jannat Patel
46209023ce Merge pull request #27985 from frappe/mergify/bp/version-13-pre-release/pr-27850 2021-10-18 10:32:48 +05:30
marination
e69bd39cdd test: Include serial no with spaces in it in sanitation test
(cherry picked from commit a9341672cf)
2021-10-18 04:56:11 +00:00
Marica
0fcb3cd918 fix: Use strip instead of lstrip and rstrip
Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
(cherry picked from commit 8cf188d9c0)
2021-10-18 04:56:10 +00:00
marination
44ab131792 fix: Retain space inside Serial no string while cleaning serial nos
(cherry picked from commit 41035b0330)
2021-10-18 04:56:10 +00:00
pateljannat
b648d77316 fix: exclude inactive employees from auto attendance
(cherry picked from commit 921b4be348)
2021-10-18 04:35:18 +00:00
Deepesh Garg
0012f0b2da fix: Account number and name incorrectly import using COA importer
(cherry picked from commit 17a8649500)
2021-10-18 03:47:22 +00:00
Deepesh Garg
1796f09c0f fix: TDS round off not working from second transaction
(cherry picked from commit b7a08535b5)
2021-10-18 03:24:46 +00:00
Deepesh Garg
64b58b148f fix: TDS round off not working from second transaction
(cherry picked from commit ca0067212d)
2021-10-18 03:24:46 +00:00
Deepesh Garg
4415bf9968 fix: Interstate internal transfer invoices not visible in GSTR-1
(cherry picked from commit d9d42b13ab)
2021-10-18 03:23:55 +00:00
rohitwaghchaure
1b7d94d70d Merge pull request #27961 from rohitwaghchaure/merge-13-hotfix-to-pre-release-for-13-13
chore: Merge branch 'version-13-hotfix' into 'version-13-pre-release'
2021-10-14 19:17:22 +05:30
Rohit Waghchaure
952c60b3f5 Merge branch 'version-13-hotfix' into 'version-13-pre-release' 2021-10-14 18:31:09 +05:30
rohitwaghchaure
8c33103838 Merge pull request #27958 from frappe/mergify/bp/version-13-hotfix/pr-27954
fix: value_after_depreciation calculation (backport #27954)
2021-10-14 18:25:57 +05:30
mergify[bot]
a8c966eb25 fix: patch to enable scheduled job for reposting (backport #27957)
* fix: patch to enable scheduled job for reposting

(cherry picked from commit efc60ec2b5)

# Conflicts:
#	erpnext/patches.txt

* chore: formatting

(cherry picked from commit 3f97413814)

* Update enable_scheduler_job_for_item_reposting.py

(cherry picked from commit 230a5d4b39)

* fix: resolve conflict

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2021-10-14 17:31:51 +05:30
Saqib
88b1c1c87e fix: value_after_depreciation calculation (#27954)
(cherry picked from commit 1f70dd6e98)
2021-10-14 10:53:52 +00:00
Marica
771213c415 Merge pull request #27955 from frappe/mergify/bp/version-13-hotfix/pr-27947
fix: Improve error message for Serial No mismatch between SI and DN (backport #27947)
2021-10-14 14:24:54 +05:30
marination
d86f5ec1ba fix: Remove trailing space and line break in translatable string
(cherry picked from commit 60f35ad8a2)
2021-10-14 08:19:54 +00:00
marination
a568fc7924 fix: Improve error message for Serial No mismatch between SI and DN
(cherry picked from commit 646acb6b46)
2021-10-14 08:19:53 +00:00
mergify[bot]
c040256793 fix: cannot add deductions in internal transfer payment entry (backport #27545) (#27930)
* fix: cannot add deductions in internal transfer payment entry

(cherry picked from commit 1b7414e948)

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

* fix: Update message string

(cherry picked from commit 3b9514d6e1)

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

* fix: conflicts

Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com>
2021-10-14 12:34:03 +05:30
Chillar Anand
29996ee726 fix(hr): Update expense account after company is updated (#27843) (#27919)
(cherry picked from commit f0c4ea14a9)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-10-14 11:13:00 +05:30
Deepesh Garg
1120506e11 Merge pull request #27941 from frappe/mergify/bp/version-13-hotfix/pr-27783
fix(Subscription): reorder updation of end date (backport #27783)
2021-10-13 16:56:07 +05:30
mergify[bot]
e64751e3a2 fix: not authorized to update entries after freezing accounts (backport #27937)
* fix: not authorized to update entries after freezing accounts (#27937)

* fix: not authorized to update entries after freezing accounts

* fix: Add test case

* fix(patch): patched to requeue failed reposts(check_freezing_date)

* chore: misc fixes

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
(cherry picked from commit 2bb383b178)

# Conflicts:
#	erpnext/patches.txt

* fix: resolve conflict

Co-authored-by: Noah Jacob <noahjacobkurian@gmail.com>
Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-13 16:41:21 +05:30
Himanshu
bf47c6836e Update subscription.py
(cherry picked from commit 44306bd0e5)
2021-10-13 09:52:21 +00:00
hrwx
e80192e2da fix: create past invoices
(cherry picked from commit ae657c7e4e)
2021-10-13 09:52:21 +00:00
Sagar Vora
b2f1b02e34 test: use test_dependencies instead of duplication
(cherry picked from commit 656015d99d)
2021-10-13 09:52:20 +00:00
Sagar Vora
ed090f2e3e fix: remove newline
(cherry picked from commit fc375c5bde)
2021-10-13 09:52:20 +00:00
hrwx
d796172249 fix: reorder updation of end date
(cherry picked from commit 0f03b19109)
2021-10-13 09:52:20 +00:00
Deepesh Garg
ff9f6366ad Merge pull request #27935 from frappe/mergify/bp/version-13-hotfix/pr-27907
feat: HSN based tax breakup table check in GST Settings  (backport #27907)
2021-10-13 12:56:00 +05:30
Deepesh Garg
b8683d5532 Merge pull request #27925 from frappe/mergify/bp/version-13-hotfix/pr-27758
perf: Add indexes in stock queries and speed up bin updation (backport #27758)
2021-10-13 12:08:38 +05:30
Subin Tom
4a156cdc2e added new section in gst settings page
(cherry picked from commit fc4facc5dc)
2021-10-13 06:38:27 +00:00
Subin Tom
771b076448 feat: HSN wise tax breakup check in GST Settings
(cherry picked from commit 530de12b07)
2021-10-13 06:38:26 +00:00
mergify[bot]
e6346ac982 fix: minor ux fixes in Sales & Purchase Invoice (backport #27927) (#27932)
* fix: keeping sections consistent across sales & purchase invoice

(cherry picked from commit 2bc1ca993a)

* fix: set collapsible & print hide

(cherry picked from commit d181cc42a1)

Co-authored-by: Anuja Pawar <anuja.pawar20@gmail.com>
2021-10-13 11:01:21 +05:30
Ankush Menat
547e173fe0 ci: rule to fail PRs that add a new manual commit (#27928)
Manual commits are frequent source of bugs, confusions or undefined
behaviour.

All new manual commits should be explcitly ignored with explanation on
why it's added. This will only fail for new additions. Existing ones
need to be cleaned up manually.

(cherry picked from commit 06b426e9c3)
2021-10-12 23:05:09 +05:30
mergify[bot]
37bd0ecf87 fix: force reload custom field doctype (#27909) (#27910)
custom_field.json has the same modified key in both versions but not the same content. This can happen again if something is backported, safe solution is to force reload.

(cherry picked from commit ad444153cc)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-12 20:38:09 +05:30
Marica
8244980d79 Merge pull request #27924 from marination/item-configure-empty-popup
fix: Item Variant selection empty popup on website
2021-10-12 20:32:18 +05:30
Deepesh Garg
10b239ec50 perf: Add indexes in stock queries and speed up bin updation #27758
perf: Add indexes in stock queries and speed up bin updation
(cherry picked from commit 6f107da165)
2021-10-12 14:46:26 +00:00
marination
c9c4a9995b fix: Item Variant selection empty popup on website
- pass item_code instead of website item name to fetch attributes
2021-10-12 19:56:12 +05:30
Ankush Menat
b6dc71679e fix: remove transaction commit from tests
(cherry picked from commit 8d69ec72a6)
2021-10-12 16:41:48 +05:30
Ankush Menat
5bdb6041b9 refactor: rollback after full test
(cherry picked from commit acdb26a4bb)
2021-10-12 16:41:48 +05:30
Ankush Menat
89828defc5 test: add custom TestCase class and use in stock
(cherry picked from commit 06fa35a9c1)
2021-10-12 16:41:48 +05:30
Deepesh Garg
816236b587 Merge pull request #27918 from frappe/mergify/bp/version-13-hotfix/pr-27884
fix: patch fails if accounts are frozen (backport #27884)
2021-10-12 15:04:01 +05:30
Deepesh Garg
69f17721ef Merge pull request #27917 from frappe/mergify/bp/version-13-hotfix/pr-27896
fix: Status check for closed loans (backport #27896)
2021-10-12 15:03:31 +05:30
Afshan
125bb1f99a Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-27884 2021-10-12 14:32:36 +05:30
mergify[bot]
6f786b42a9 fix: add cost center in gl entry for advance payment entry (#27840) (#27915)
(cherry picked from commit 569dc5f6b1)

Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com>
2021-10-12 14:31:50 +05:30
Saqib Ansari
fed80177de fix: rollback on exception
(cherry picked from commit c103f72fad)
2021-10-12 08:15:17 +00:00
Saqib Ansari
2ce36d1edc feat: handle exceptions
(cherry picked from commit 353ad5f6ff)
2021-10-12 08:15:17 +00:00
Saqib Ansari
7e44c30404 fix: patch fails if accounts are frozen
(cherry picked from commit b0aa4a6e1c)
2021-10-12 08:15:16 +00:00
Deepesh Garg
47ced6810f fix: Linting issues
(cherry picked from commit af14ba43de)
2021-10-12 08:11:47 +00:00
Deepesh Garg
6c3f5687f2 fix: Incorrect maximum loan amount update
(cherry picked from commit 8355af6dcf)
2021-10-12 08:11:47 +00:00
Deepesh Garg
1b632b683f fix: Status check for closed loans
(cherry picked from commit 3337ae120c)
2021-10-12 08:11:46 +00:00
mergify[bot]
c5660e8511 Merge pull request #27906 from Anuja-pawar/accounts-settings (#27912)
fix(Accounts Settings): Update label

(cherry picked from commit e1967870a9)

Co-authored-by: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com>
2021-10-12 12:23:53 +05:30
mergify[bot]
c4338d184e fix(accounts): Fix issue with fetching loyalty point entries (#27892) (#27913)
(cherry picked from commit 401e22fb8d)

Co-authored-by: Chillar Anand <chillar@avilpage.com>
2021-10-12 12:23:32 +05:30
Noah Jacob
d598a61556 Merge pull request #27901 from frappe/mergify/bp/version-13-hotfix/pr-27800
refactor: updated buying onboarding tours. (backport #27800)
2021-10-11 16:33:59 +05:30
Noah Jacob
d262d0ac27 refactor: updated onboarding cards and tours
(cherry picked from commit f5e0cad6a1)
2021-10-11 10:04:40 +00:00
mergify[bot]
7dc2f95932 fix: v12 migrate error - unknown column ‘mandatory_depends_on’ (backport #27897) (#27900)
* fix: v12 migrate error - unknown column ‘mandatory_depends_on’ (#27897)

* fix: v12 doesn't have mandatory_depends_on field

* fix: move update_vehicle_no_reqd_condition to v13

* fix: move update_vehicle_no_reqd_condition to v13

* fix: file name missing .py

* refactor!: add back empty line

* fix: linters issue

(cherry picked from commit 7acdcc70ad)

# Conflicts:
#	erpnext/patches.txt

* fix: resolve conflicts

Co-authored-by: Dany Robert <rtdany10@gmail.com>
Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-11 10:02:31 +00:00
rohitwaghchaure
cf87c9138e Merge pull request #27898 from frappe/mergify/bp/version-13-hotfix/pr-27863
fix: consolidated report not consider company currency (backport #27863)
2021-10-11 14:07:51 +05:30
Rohit Waghchaure
1a42f82d14 fix: opening balance to calculate 'Unclosed Fiscal Years Profit / Loss (Credit)'
(cherry picked from commit 19d14da0d4)
2021-10-11 08:37:27 +00:00
Rohit Waghchaure
2c5a0bff47 fix: consolidated report not consider company currency
(cherry picked from commit dc4206428d)
2021-10-11 08:37:26 +00:00
mergify[bot]
a831e6b552 fix: bom item query (backport #27890) (#27894)
* fix: bom item query #27890

fix: bom item query
(cherry picked from commit 0a3dd3e954)

# Conflicts:
#	erpnext/manufacturing/doctype/bom/test_bom.py

* fix: resolve conflict

* chore: unused imports

[skip ci]

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-11 12:45:24 +05:30
ChillarAnand
f844c36ab2 fix(CI): Use bugbear instead of flake8-mutable
(cherry picked from commit 4dc17a856e)
2021-10-11 11:23:22 +05:30
mergify[bot]
091c2f3023 fix(perf): index creation on voucher_detail_no (#27866) (#27875)
voucher_detail_no is supposed to have an index, it was added on
on_doctype_update function of table, however this function is only
called if DocType itself is updated and `on_update` is called on
DocType. Stock ledger Entry doctype hasn't changed since addition of
this index in function.

Before: Lack of this index was causing full table scan in
get_future_sle_to_fix function. (~50 seconds in a reposting job)

After: Single row is fetched (~0.5 second in full reposting job)

Learnings:
1. Add simple indexes via doctype only
2. For complex indexes always change doctype.json file for it to take
   effect.

(cherry picked from commit 6019f60d0a)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-08 17:19:41 +05:30
mergify[bot]
119c2f01e1 fix(perf): index creation on voucher_detail_no (#27866) (#27876)
voucher_detail_no is supposed to have an index, it was added on
on_doctype_update function of table, however this function is only
called if DocType itself is updated and `on_update` is called on
DocType. Stock ledger Entry doctype hasn't changed since addition of
this index in function.

Before: Lack of this index was causing full table scan in
get_future_sle_to_fix function. (~50 seconds in a reposting job)

After: Single row is fetched (~0.5 second in full reposting job)

Learnings:
1. Add simple indexes via doctype only
2. For complex indexes always change doctype.json file for it to take
   effect.

(cherry picked from commit 6019f60d0a)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-08 17:19:34 +05:30
gavin
40aac908d1 fix: Ignore mandatory fields if exist (#27871)
The goal of this fix is to not break the patch in case of customizations
In this particular case, it's regarding a customized Note DocType with
multiple custom mandatory fields
2021-10-08 15:10:03 +05:30
mergify[bot]
4e6d588ae1 fix: remove readonly from billing address (#27873)
(cherry picked from commit 41fefa356f)

Co-authored-by: 18alantom <2.alan.tom@gmail.com>
2021-10-08 15:08:45 +05:30
mergify[bot]
504f2f06d3 fix: Salary Slip Label fixes (backport #27865) (#27870)
* fix: Salary Slip Label fixes (#27865)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
(cherry picked from commit d3d4a3da62)

# Conflicts:
#	erpnext/payroll/doctype/salary_slip/salary_slip.json

* fix: conflicts

Co-authored-by: yadavyk <32797974+yadavyk@users.noreply.github.com>
Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-10-08 14:16:12 +05:30
mergify[bot]
d96fd60878 fix: update dead links in help_links.js (#27860) (#27868)
(cherry picked from commit 90a249527d)

Co-authored-by: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com>
2021-10-08 13:31:07 +05:30
Deepesh Garg
d9a219850a fix: SO delivery Date not getting set via data import (#27862)
* fix: SO delivery Date not getting set via data import

* fix: logic to add delivery dates

* fix: linting issue

Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com>
Co-authored-by: Afshan <afshan13k@gmail.com>
2021-10-08 12:38:40 +05:30
mergify[bot]
cb6d884058 fix(Payment Reconciliation): minor ux fixes (#27779) (#27859)
* fix: minor fixes

* fix: Linters check

* fix: sider check

* fix: kept unallocated payment amount hidden in allocation

* fix: removed Add row button from the tables (redundant)

Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com>
Co-authored-by: Saqib <nextchamp.saqib@gmail.com>
(cherry picked from commit 5cc3ea0aa7)

Co-authored-by: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com>
2021-10-07 22:47:05 +05:30
mergify[bot]
4102f799dc fix: trim sales invoice custom field lengths (backport #27665) (#27750)
* fix: trim sales invoice custom field lengths

(cherry picked from commit a7df4227da)

* patch: trim sales invoice custom field lengths

(cherry picked from commit f1fcb385f5)

# Conflicts:
#	erpnext/patches.txt

* fix: do not set length for date field

(cherry picked from commit 83cc597594)

* fix: merge conflicts

Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com>
2021-10-07 20:59:07 +05:30
Subin Tom
3c53c5b660 fix: cancelled sales invoices are considered in billed quantity calculation (#27845)
Co-authored-by: Saqib Ansari <nextchamp.saqib@gmail.com>
Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com>
2021-10-07 20:36:00 +05:30
mergify[bot]
0a4b3d8129 fix: help links for purchase cycle and JV (#27856) (#27858)
(cherry picked from commit 07c680d7cc)

Co-authored-by: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com>
2021-10-07 20:29:28 +05:30
mergify[bot]
bad489426a feat: (Stock Reco) Ignore Empty Stock while fetching items from warehouse (#27848)
- Added checkbox to `Fetch Items from Warehouse` dialog to ignore empty stock
- fix: Items fetched twice due to Item Defaults
- Improved code readability

(cherry picked from commit 533ee9a401)

Co-authored-by: marination <maricadsouza221197@gmail.com>
2021-10-07 20:28:08 +05:30
mergify[bot]
729e29d268 fix: update help links for Sales Invoice page (#27853) (#27854)
(cherry picked from commit 646fd29f0e)

Co-authored-by: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com>
2021-10-07 18:42:06 +05:30
rohitwaghchaure
8108d4761b Merge pull request #27852 from frappe/mergify/bp/version-13-hotfix/pr-27851
feat: option to set the width for the multi-select dialog box (backport #27851)
2021-10-07 17:39:20 +05:30
Rohit Waghchaure
00cb04df84 feat: option to set the width for the multi-select dialog box
(cherry picked from commit 69ffddf747)
2021-10-07 12:08:58 +00:00
mergify[bot]
dd0cefbeb9 refactor: Clean up mutable defaults and add CI check (#27828) (#27841)
* refactor: Clean up mutable defaults and add CI check

(cherry picked from commit 772d4753e7)

Co-authored-by: Chillar Anand <chillar@avilpage.com>
2021-10-07 09:57:35 +00:00
Rohit Waghchaure
d11c215f85 Merge branch 'version-13-pre-release' into version-13 2021-10-07 11:35:31 +05:30
Rohit Waghchaure
5ea5bfad3d bumped to version 13.12.1 2021-10-07 11:55:31 +05:50
Anoop
4da5cb36e7 Merge pull request #27812 from akurungadam/fix-appointment-slots
fix(healthcare): Availability slots display, disabled Practitioner Schedule
2021-10-07 08:45:56 +05:30
mergify[bot]
56b58cbeea fix: use ceil in case of whole uoms for reorder qty (#27834) (#27838)
* fix: use ceil in case of whole uoms for reorder qty

* fix: cache uom query

(cherry picked from commit d4b2471cea)

Co-authored-by: Alan <2.alan.tom@gmail.com>
2021-10-06 18:54:22 +05:30
Marica
cd87931cee Merge pull request #27833 from frappe/mergify/bp/version-13-pre-release/pr-27713
fix: Maintenance Schedule child table status for legacy data (backport #27713)
2021-10-06 14:08:15 +05:30
Marica
355ae1fca6 Merge branch 'version-13-pre-release' into mergify/bp/version-13-pre-release/pr-27713 2021-10-06 13:42:46 +05:30
mergify[bot]
91baa22d59 fix: remove stale doctypes and add msg for ecommerce refactor (bp #27700)
(cherry picked from commit 6d99bb5ce6)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-06 13:37:26 +05:30
marination
af57e1e299 fix: reload doc in patch
(cherry picked from commit 6b38778dcb)
2021-10-06 07:57:22 +00:00
marination
1d3ba46107 fix: Add patch to patches.txt
(cherry picked from commit 7c47f36a4c)
2021-10-06 07:57:22 +00:00
Marica
65a590ced1 fix: Maintenance Schedule child table status for legacy data (#27554)
* fix: Maintenance Schedule child table status for legacy data

* fix: Include legacy draft schedules in patch

* fix: Pre-commit formatting

(cherry picked from commit cc143bca0d)
(cherry picked from commit 6ce2111b6d)
2021-10-06 07:57:21 +00:00
Marica
1a68e10742 Merge pull request #27829 from frappe/mergify/bp/version-13-pre-release/pr-27715
fix: Batch scans get overwritten on the same row (backport #27668) (backport #27715)
2021-10-06 12:57:39 +05:30
Marica
71f676eedd Merge pull request #27764 from frappe/mergify/bp/version-13-hotfix/pr-27661
refactor: fetching of account balance in chart of accounts (backport #27661)
2021-10-06 12:56:10 +05:30
mergify[bot]
4fe827e86b Merge pull request #27715 from frappe/mergify/bp/version-13-hotfix/pr-27668
fix: Batch scans get overwritten on the same row (backport #27668)
(cherry picked from commit 15c9c08261)
2021-10-06 07:13:00 +00:00
Deepesh Garg
f573840c9f Merge pull request #27801 from frappe/mergify/bp/version-13-pre-release/pr-27524
fix: Tax breakup based on items, missing GST fields (backport #27524)
2021-10-06 09:33:30 +05:30
Rohit Waghchaure
0bc83deca0 Merge branch 'version-13-pre-release' into version-13 2021-10-06 00:26:31 +05:30
Rohit Waghchaure
696a01765b bumped to version 13.12.0 2021-10-06 00:46:31 +05:50
rohitwaghchaure
047debffeb Merge pull request #27825 from rohitwaghchaure/v13_12_0-change-log
chore: change log for v13_12_0
2021-10-06 00:02:19 +05:30
mergify[bot]
e57037b4cf ci: fail build if asset bundling fails (#27820) (#27823)
(cherry picked from commit 35e30bdcaf)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-05 23:59:47 +05:30
Rohit Waghchaure
633cd0c1a8 chore: change log for v13_12_0 2021-10-05 23:44:32 +05:30
Deepesh Garg
51b2fcc8ef fix: Update setup.py 2021-10-05 21:05:12 +05:30
Deepesh Garg
ff062d501f fix: Update patches.txt 2021-10-05 21:03:40 +05:30
mergify[bot]
2cbd5a9fcf fix: removed redundant piece of code (#27817) (#27821)
(cherry picked from commit cec66d2d10)

Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com>
2021-10-05 19:26:58 +05:30
mergify[bot]
89e421a01d fix: removed redundant piece of code (#27817) (#27822)
(cherry picked from commit cec66d2d10)

Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com>
2021-10-05 19:24:40 +05:30
Noah Jacob
287a7b29e1 Merge pull request #27815 from frappe/mergify/bp/version-13-pre-release/pr-27813
fix: ignore random periodicity in validations (backport #27813)
2021-10-05 17:32:09 +05:30
Noah Jacob
47a7eeca54 Merge pull request #27814 from frappe/mergify/bp/version-13-hotfix/pr-27813
fix: ignore random periodicity in validations (backport #27813)
2021-10-05 17:30:00 +05:30
Ankush Menat
a09647b3c8 fix: ignore random periodicity in validations
(cherry picked from commit 3d3655ed73)
2021-10-05 11:38:06 +00:00
Ankush Menat
62bbf0fe45 fix: ignore random periodicity in validations
(cherry picked from commit 3d3655ed73)
2021-10-05 11:28:34 +00:00
Marica
ae8c1ae311 Merge pull request #27803 from frappe/mergify/bp/version-13-hotfix/pr-27660
fix: tax rate being overridden in case of 0.00 (backport #27660)
2021-10-05 16:44:16 +05:30
Deepesh Garg
c1c5aa7262 Merge pull request #27810 from frappe/mergify/bp/version-13-pre-release/pr-27792
fix: COA Importer showing blank validations (backport #27792)
2021-10-05 15:39:29 +05:30
Deepesh Garg
b4f06c0d77 fix: Use get_list instead of get_all to avoid perm issues
(cherry picked from commit 9507b2d752)
2021-10-05 09:48:42 +00:00
Deepesh Garg
758939eddb fix: COA Importer showing blank validations
(cherry picked from commit 0660d6ed01)
2021-10-05 09:48:42 +00:00
Deepesh Garg
8f5ab94b70 Merge pull request #27792 from deepeshgarg007/pre-release-fixes
fix: COA Importer showing blank validations
2021-10-05 15:18:04 +05:30
mergify[bot]
fa944382c5 fix: using DN for transfer w/o internal customer (backport #27798) (#27805)
* fix: using DN for transfer w/o internal customer (#27798)

This used to be work before though not "advertised", since a lot of
users have started using it as feature, it can't be broken now.

(cherry picked from commit df1f8fddf6)

* fix(ux): use toast instead of popup

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-05 14:53:26 +05:30
Deepesh Garg
9507b2d752 fix: Use get_list instead of get_all to avoid perm issues 2021-10-05 14:51:35 +05:30
mergify[bot]
7e018f94ce fix: batch_no not mapped from PR to Stock Entry (#27804)
(cherry picked from commit 9613af6c4e)

Co-authored-by: Noah Jacob <noahjacobkurian@gmail.com>
2021-10-05 14:39:36 +05:30
Ankush Menat
2cfafede44 fix(ux): use toast instead of popup 2021-10-05 14:27:09 +05:30
Ankush Menat
df1f8fddf6 fix: using DN for transfer w/o internal customer (#27798)
This used to be work before though not "advertised", since a lot of
users have started using it as feature, it can't be broken now.
2021-10-05 14:21:27 +05:30
Dany Robert
a17fed9cd9 fix: return tax rate since fetch is removed
(cherry picked from commit 2b4959fb3b)
2021-10-05 08:45:24 +00:00
Dany Robert
d8479a41e5 fix: tax rate being overridden in case of 0.00
Tax rate could be different for different expenses.
Therefore, rate is kept as 0.00 and tax amount entered manually.
But fetching used to override the rate(upon saving) and mess up the amount.

(cherry picked from commit 5ce6a4c107)
2021-10-05 08:45:23 +00:00
Subin Tom
b1244df045 fix: Tax breakup based on items, missing GST fields (#27524)
* fix: Tax breakup based on items

* fix: added gst fields,warehouse validation to pos inv,patch

* fix: tax breakup test fix, eway bill hsn fix

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
(cherry picked from commit d49346ac45)

# Conflicts:
#	erpnext/patches.txt
#	erpnext/regional/india/setup.py
2021-10-05 08:44:31 +00:00
Deepesh Garg
d2f5d31f98 Merge pull request #27559 from frappe-pr-bot/backport/version-13-hotfix/27524
fix: Tax breakup based on items, missing GST fields
2021-10-05 14:13:50 +05:30
Deepesh Garg
28aa9a7215 Merge pull request #27761 from frappe/mergify/bp/version-13-pre-release/pr-27712
fix(India): Internal transfer check fix (backport #27712)
2021-10-05 14:10:57 +05:30
Deepesh Garg
44ee44dec5 Merge pull request #27786 from frappe/mergify/bp/version-13-hotfix/pr-27785
fix: Delete linked Transaction Deletion Record docs on deleting company (backport #27785)
2021-10-05 14:09:16 +05:30
mergify[bot]
bebd77c27d fix: add (uom, brand) Item details in an Item Price (#27561) (#27795)
* fix: add (uom, brand) and update (uom) Item details in an Item Price

* fix: order of query interpolation args

Co-authored-by: Marica <maricadsouza221197@gmail.com>

* fix: named interpolation, remove item price

* fix: sql error

Co-authored-by: Marica <maricadsouza221197@gmail.com>
Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
(cherry picked from commit 7da777880b)

Co-authored-by: Alan <2.alan.tom@gmail.com>
2021-10-05 13:28:43 +05:30
Afshan
0c55a98190 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-27785 2021-10-05 13:26:47 +05:30
mergify[bot]
32d72fdecb fix: Only calculate first_respone_time if SLA is set (#27789) (#27793)
Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com>
(cherry picked from commit ad03eb25df)

Co-authored-by: Ganga Manoj <ganga.manoj98@gmail.com>
2021-10-05 12:49:06 +05:30
mergify[bot]
046ec928e0 fix: Display appropriate message if different Payment Terms are used in PE and its Payment References (#27763)
(cherry picked from commit 9f14695743)

Co-authored-by: GangaManoj <ganga.manoj98@gmail.com>
Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com>
2021-10-05 12:21:21 +05:30
Deepesh Garg
0660d6ed01 fix: COA Importer showing blank validations 2021-10-05 12:20:14 +05:30
Afshan
877820b902 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-27785 2021-10-05 11:50:25 +05:30
mergify[bot]
ab0e381cfc fix(asset): expected value after useful life validation (#27539) (#27790)
(cherry picked from commit 065a2ce983)

Co-authored-by: Saqib <nextchamp.saqib@gmail.com>
2021-10-05 11:47:56 +05:30
Saqib
5e34cdf00f Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-27661 2021-10-05 11:34:02 +05:30
Saqib
953073bc77 fix(asset): expected value after useful life validation (#27787) 2021-10-05 11:13:50 +05:30
GangaManoj
4ecb798585 fix: Delete linked Transaction Deletion Record docs on deleting company
(cherry picked from commit 38c7e42f0c)
2021-10-05 05:19:37 +00:00
mergify[bot]
91d269fe1a fix: set item uom as stock_uom if it isn't set (#27623) (#27780)
* fix: set item uom as stock_uom if it isn't set

(cherry picked from commit 5c372202d5)

Co-authored-by: Alan <2.alan.tom@gmail.com>
2021-10-04 22:41:12 +05:30
mergify[bot]
4535a9415f ci(Mergify): configuration update (#27777) (#27778)
Signed-off-by: Ankush Menat <me@ankush.dev>
(cherry picked from commit 4159361d52)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-04 18:10:52 +05:30
Sagar Vora
e17713c9d6 fix: multiple fixes to timesheets (#27775) 2021-10-04 17:08:50 +05:30
mergify[bot]
21a5498d5d fix: Merge "Accounting Ledger" and "Accounts Receivable" in "View" button (#27769) (#27771)
* fix: Added a new button "View" and merged "Accounting Ledger" and "Accounts Receivable" into it

* fix: sider issues

* chore: dead code

(cherry picked from commit b483f173a6)

Co-authored-by: Komal-Saraf0609 <81952590+Komal-Saraf0609@users.noreply.github.com>
2021-10-04 16:06:55 +05:30
Saqib
c0b17edbbf perf: fetching of account balance in chart of accounts (#27661)
(cherry picked from commit 9051735529)
2021-10-04 06:15:34 +00:00
Deepesh Garg
e6909a0899 fix(India): Internal transfer check fix
(cherry picked from commit f0af24fc6d)
2021-10-04 05:31:59 +00:00
Marica
68cce8eb98 Merge pull request #27757 from frappe/mergify/bp/version-13-pre-release/pr-27720
fix: Website Items with same Item name unhandled, thumbnails missing (backport #27720)
2021-10-03 14:46:18 +05:30
Marica
869c1beb0a Merge pull request #27744 from frappe/mergify/bp/version-13-pre-release/pr-27611
fix: Hero Slider Control & Alignment fixes (backport #27611)
2021-10-03 14:29:39 +05:30
Marica
e9ed379b57 Merge pull request #27743 from frappe/mergify/bp/version-13-hotfix/pr-27611
fix: Hero Slider Control & Alignment fixes (backport #27611)
2021-10-03 14:29:18 +05:30
marination
a453fc6385 fix: Pre-commit formatting
(cherry picked from commit 77d4849ce8)
2021-10-03 08:57:13 +00:00
marination
00978a195a fix: Website Items with same Item name unhandled, thumbnails missing
- Use naming series for Website Item. There could be two items with same name and different item code
- Fix: Website Item Page view breaks if cart is disabled
- Fix: thumbnails not created for Website Items after patch
- Fix: ‘Request for Quote’ button & cart summary not visible if checkout is disabled

(cherry picked from commit 36b519c962)
2021-10-03 08:57:12 +00:00
Marica
4d0d642db7 Merge pull request #27720 from marination/e-comm-web-item-name-thumbnail-fix
fix: Website Items with same Item name unhandled, thumbnails missing
2021-10-03 14:26:20 +05:30
Deepesh Garg
f02438eb54 Merge pull request #27712 from deepeshgarg007/internal_transfer_check_fix
fix(India): Internal transfer check fix
2021-10-03 13:38:51 +05:30
Marica
62fa1f0305 Merge branch 'version-13-hotfix' into e-comm-web-item-name-thumbnail-fix 2021-10-03 13:30:50 +05:30
marination
77d4849ce8 fix: Pre-commit formatting 2021-10-03 13:30:02 +05:30
Deepesh Garg
66286881f3 Merge pull request #27752 from frappe/mergify/bp/version-13-pre-release/pr-27748
fix: Chart Of Accounts import button not visible (backport #27748)
2021-10-02 22:16:15 +05:30
Deepesh Garg
c2e4bbe9cc fix: Linting issues
(cherry picked from commit ff570f48a0)
2021-10-02 16:10:41 +00:00
Deepesh Garg
02984551ce fix: Remove unwanted comments
(cherry picked from commit e4b89d2fcd)
2021-10-02 16:10:40 +00:00
Deepesh Garg
42f0a97d26 fix: Chart Of Accounts import button not visible
(cherry picked from commit 3529622a0d)
2021-10-02 16:10:39 +00:00
Deepesh Garg
fe4df3a14a Merge pull request #27748 from deepeshgarg007/yet_chart_of_accounts_importer_fixes
fix: Chart Of Accounts import button not visible
2021-10-02 21:39:07 +05:30
Deepesh Garg
ff570f48a0 fix: Linting issues 2021-10-02 20:46:20 +05:30
Deepesh Garg
e4b89d2fcd fix: Remove unwanted comments 2021-10-02 20:37:15 +05:30
Deepesh Garg
3529622a0d fix: Chart Of Accounts import button not visible 2021-10-02 20:35:11 +05:30
Shariq Ansari
477d36413b chore: linter fix
(cherry picked from commit 18918e1b4f)
2021-10-02 11:39:01 +00:00
Shariq Ansari
b5fc3076bf fix: Fixed alignment of Title, Subtitle, Action Button
(cherry picked from commit 0de735f20b)
2021-10-02 11:39:00 +00:00
Shariq Ansari
2c9162160a fix: Creating unique hash for slider id instead of slider name
(cherry picked from commit 3e8e6ac4e2)
2021-10-02 11:38:59 +00:00
Shariq Ansari
8f98238114 chore: linter fix
(cherry picked from commit 18918e1b4f)
2021-10-02 11:38:31 +00:00
Shariq Ansari
86e3adf344 fix: Fixed alignment of Title, Subtitle, Action Button
(cherry picked from commit 0de735f20b)
2021-10-02 11:38:31 +00:00
Shariq Ansari
d6152df3b4 fix: Creating unique hash for slider id instead of slider name
(cherry picked from commit 3e8e6ac4e2)
2021-10-02 11:38:30 +00:00
Anurag Mishra
4837238f3d feat(HR): Some Enhancements and Onboarding (#25741)
* feat: Hr settings restructure

* feat: remove validation and make As warning

* feat: made leave policy Assignment feild read only

* feat: send leave Notification via 'Notification'

* patch: for field name change

* feat: removed defaults value for removed field

* feat: removed leave Notification fields

* feat: better label and description

* feat: Hr Module onboarding and Onboarding slides

* fix: sider, test, translations

* chore: remove unnecessary code formatting changes

* refactor: HR Onboarding

* refactor: HR Settings

* revert: Notification changes

* chore: remove unnecessary descriptions from leave type

* fix: linter issues

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-10-02 12:34:21 +05:30
mergify[bot]
e4f12f0458 fix: update variant qty in BOM, Create Work Order dialog (#27686) (#27732)
(cherry picked from commit ece446ffe5)

Co-authored-by: Alan <2.alan.tom@gmail.com>
2021-10-01 23:21:08 +05:30
mergify[bot]
3e7a029869 fix: option to limit reposting in certain timeslot (bp #27725)
(cherry picked from commit a04f9c904e)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-01 16:50:05 +05:30
mergify[bot]
23431cf261 fix: option to limit reposting in certain timeslot (#27725) (#27726)
(cherry picked from commit a04f9c904e)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-10-01 13:56:00 +05:30
Anurag Mishra
57e66f958c feat: Tracking Multi-round interview (#25482)
* feat: Tracking Multi-round interview

* fix: releted to scheduler event and formating

* fix: job applicant UI/UX and conflicts

* test: Interview Round

* fix(test): Employee referral, Employee Onboarding, Job Offer

* fix: sider

* feat: set default value in Hr settings

* feat: added validation for designation

* test: Interview

* test: Added validatiolns for skill

* test: Interview feedback

* fix: sider

* fix: remove unnecessary validations and form label cleanups

* chore: clean-up Interview Round and Interview Type doctype

* fix: remove redundant Rating Value, only keep Rating

* fix: update interview details on feedback submission

- make interview feedback submission dialog minimizable

* fix: show submit feedback button only if feedback doesn't exist

* refactor: Interview and Feedback statuses and workflow

* fix(HR Settings): clean up interview settings

* refactor: Interview

* refactor: Interview Feedback, remove unnecessary validations

* chore: update notification messages

* chore: remove unnecessary formatting changes in attendance list and leave application

* refactor: Job Applicant to Interview mapping

* chore: sorted imports

* chore: sorted imports

* fix: sider issues

* fix: linter issues

* fix: sider issues

* fix: tests

* fix: sorted imports

* fix: tests, sider

* fix: therapy plan test

* fix: sider issues

* feat: Include From Time and To Time fields in Interview for cleaner data

* feat: Interview Calendar

* fix: allow renaming masters

* fix: add more fields to list view and standard filter

* fix: validate overlapping interviews

* fix: update tests

* fix: linter issues

* refactor: replace reminder messages with Email Templates

* fix: sider issues

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-10-01 00:10:47 +05:30
mergify[bot]
05374cb8b2 fix(Org Chart): use attribute selectors instead of ID selector for node IDs with special characters (#27717) (#27718)
* fix(Org Chart): use attribute selectors instead of ID selector for node IDs with special chars

* fix: UI tests

(cherry picked from commit 9e08229b7b)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-09-30 18:45:22 +05:30
mergify[bot]
f572a4e0e5 fix(Org Chart): use attribute selectors instead of ID selector for node IDs with special characters (#27717) (#27719)
* fix(Org Chart): use attribute selectors instead of ID selector for node IDs with special chars

* fix: UI tests

(cherry picked from commit 9e08229b7b)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-09-30 18:44:55 +05:30
marination
36b519c962 fix: Website Items with same Item name unhandled, thumbnails missing
- Use naming series for Website Item. There could be two items with same name and different item code
- Fix: Website Item Page view breaks if cart is disabled
- Fix: thumbnails not created for Website Items after patch
- Fix: ‘Request for Quote’ button & cart summary not visible if checkout is disabled
2021-09-30 18:34:26 +05:30
rohitwaghchaure
348f082490 Merge pull request #27716 from rohitwaghchaure/merge-v13-hotfix-to-pre-release
chore: Merge branch 'version-13-hotfix' into 'version-13-pre-release'
2021-09-30 18:07:47 +05:30
Rohit Waghchaure
49174cd414 chore: Merge branch 'version-13-hotfix' into 'version-13-pre-release' 2021-09-30 16:59:18 +05:30
mergify[bot]
15c9c08261 Merge pull request #27715 from frappe/mergify/bp/version-13-hotfix/pr-27668
fix: Batch scans get overwritten on the same row (backport #27668)
2021-09-30 16:34:11 +05:30
Marica
5d1de91b68 Merge pull request #27713 from frappe-pr-bot/backport/version-13-hotfix/27554
fix: Maintenance Schedule child table status for legacy data
2021-09-30 15:57:27 +05:30
Marica
cf6e10ac7b Merge branch 'version-13-hotfix' into backport/version-13-hotfix/27554 2021-09-30 15:55:04 +05:30
Ankush Menat
6d99bb5ce6 fix: remove stale doctypes and add msg for ecommerce refactor (#27700) 2021-09-30 15:49:26 +05:30
marination
6b38778dcb fix: reload doc in patch 2021-09-30 14:55:57 +05:30
marination
7c47f36a4c fix: Add patch to patches.txt 2021-09-30 14:18:35 +05:30
Marica
6ce2111b6d fix: Maintenance Schedule child table status for legacy data (#27554)
* fix: Maintenance Schedule child table status for legacy data

* fix: Include legacy draft schedules in patch

* fix: Pre-commit formatting

(cherry picked from commit cc143bca0d)
2021-09-30 08:39:55 +00:00
Deepesh Garg
f0af24fc6d fix(India): Internal transfer check fix 2021-09-30 13:28:53 +05:30
Mohammed Yusuf Shaikh
b478e72cef fix: wrong company selected when marking attendance for all employees (#27685)
* fix: wrong company selected when marking attendance for all employees

* fix: enable caching for repeated queries of the same employee

Co-authored-by: Ankush Menat <ankushmenat@gmail.com>

Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
2021-09-30 12:02:39 +05:30
mergify[bot]
471a8ddce0 fix: distribution of additional costs in mfg stock entry (#27629) (#27706)
* refactor: remove unnecessary list comprehensions

* fix: correct cost distribution logic

While apportioning costs same condition should be present on both sides
so total value is representative of all items to be apportioned.

Here while calculating incoming_items_cost only FG items are considered,
but while apportioning all items with to_warehouse are considered.

Solution: only apportion additional cost on FG items

* test: test cost distribution

* fix: patch for additional cost

fix(patch): consider PCV while patching

- consider Period closing voucher while patching
- recomute rates for SLE of affected stock entries

consider only FG/scrap item SLEs for recomputation of rates

* fix: remove client side logic for addn cost

All of this is done in python code hence removed client side code.

(cherry picked from commit 4685ed5a8c)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-30 11:20:09 +05:30
Deepesh Garg
7fc7405be7 fix: Deferred revenue entries post account freezing (#27551)
* fix: Deferred revenue entries post account freezing

* fix: Test Case

* fix: Test case
2021-09-30 08:35:30 +05:30
rohitwaghchaure
26c16412bb Merge pull request #27702 from frappe/mergify/bp/version-13-hotfix/pr-27701
fix: added project name in the purchase order analysis (backport #27701)
2021-09-29 23:43:54 +05:30
Rohit Waghchaure
c8396f4970 fix: added project name in the purchase order analysis
(cherry picked from commit c1f9997a67)
2021-09-29 18:13:25 +00:00
Deepesh Garg
aeffaf312a Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into deferred_entry_freeze_v13 2021-09-29 22:27:02 +05:30
Deepesh Garg
23863c7663 fix: Test case 2021-09-29 22:26:33 +05:30
Frappe PR Bot
887e3765bf feat: TDS deduction using journal entry and other fixes (#27451) (#27672)
* fix: TDS deduction using journal entry

* fix: Multi category application against single supplier

* refactor: TDS payable monthly report

* fix: Server side handling for default tax withholding category

* fix: Supplier filter for Journal Entry

* refactor: TDS computation summary report

(cherry picked from commit cc5dd5c67d)

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
2021-09-29 20:26:43 +05:30
Frappe PR Bot
633847c514 fix: Ignore user permission for Represents Company field in Sales and Purchase docs (#27684) (#27695)
* fix: Ignore user permission for Represents Company field in Sales and Purchase docs

* fix: Ignore user permission for fiscal year company

(cherry picked from commit b39f8a6215)

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
2021-09-29 20:26:16 +05:30
Deepesh Garg
72986abab9 Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into deferred_entry_freeze_v13 2021-09-29 16:13:28 +05:30
mergify[bot]
6b32fa7b1c feat(regional): toggle for reduced depreciation rate as per IT Act (#27688) 2021-09-28 19:48:50 +05:30
Frappe PR Bot
212492220d fix: cannot delete a project if linked with sales order (#27536) (#27689) 2021-09-28 19:46:35 +05:30
Frappe PR Bot
fbae3c7fd6 fix: apply price list after batch or serial no insertion (#27566) (#27683)
(cherry picked from commit 72c081fd8f)

Co-authored-by: Alan <2.alan.tom@gmail.com>
2021-09-28 12:59:45 +05:30
Frappe PR Bot
745abef0e7 fix: set item.qty as mandatory in picklist (#27680) (#27682)
(cherry picked from commit b91333afdd)

Co-authored-by: Alan <2.alan.tom@gmail.com>
2021-09-28 12:24:55 +05:30
Frappe PR Bot
1acdadea00 fix(ux): added exception of template item in filters (#27560) (#27675)
(cherry picked from commit 5c249decbb)

Co-authored-by: Noah Jacob <noahjacobkurian@gmail.com>
2021-09-27 23:01:14 +05:30
mergify[bot]
2ee9da3504 fix: cannot set custom label for 'total' field in print format (#27667) 2021-09-27 15:03:16 +05:30
Deepesh Garg
6eb9a114be Merge branch 'version-13-hotfix' into backport/version-13-hotfix/27524 2021-09-27 14:55:32 +05:30
Frappe PR Bot
e8de8b8454 fix: Improvements in COA Importer (#27584) (#27587)
(cherry picked from commit f07ff92a35)

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
2021-09-27 14:53:51 +05:30
Frappe PR Bot
1b9e96f760 fix: Tax Breakup table headers fix (#27596) (#27598)
(cherry picked from commit 0ff7367f39)

Co-authored-by: Subin Tom <36098155+nemesis189@users.noreply.github.com>
Co-authored-by: Saqib <nextchamp.saqib@gmail.com>
2021-09-27 14:53:29 +05:30
Saqib
0e59873cc0 Merge pull request #27663 from frappe/mergify/bp/version-13-hotfix/pr-27659
fix: setting of gain/loss if party account is in company currency (backport #27659)
2021-09-27 14:29:25 +05:30
Saqib
1063ced7cd fix: linting errors
(cherry picked from commit 711395db22)
2021-09-27 07:36:17 +00:00
Saqib
904c592978 fix: indentation
(cherry picked from commit 1f8ad72418)
2021-09-27 07:36:16 +00:00
Saqib
7a8ff85308 chore: log modified invoices
(cherry picked from commit 30f59b09fd)
2021-09-27 07:36:16 +00:00
Saqib Ansari
8a8921fb5b fix: unknown column 'ref_exchange_rate'
(cherry picked from commit 18e5d59d24)
2021-09-27 07:36:15 +00:00
Saqib Ansari
705f094a1a chore: hide exchange gain loss if empty
(cherry picked from commit e6b4d33f4b)
2021-09-27 07:36:15 +00:00
Saqib Ansari
19b06b0745 patch: invalid gain loss gl entry
(cherry picked from commit beebfb323a)
2021-09-27 07:36:15 +00:00
Saqib Ansari
2108bf9f73 fix: cost center in exchange gain loss gl entry
(cherry picked from commit 78ad50efc2)
2021-09-27 07:36:14 +00:00
Saqib Ansari
677a59b005 chore: hide exchange gain loss if empty
(cherry picked from commit dd2d039ca8)
2021-09-27 07:36:13 +00:00
Saqib Ansari
af531372da fix: setting of gain/loss if party account is in company currency
(cherry picked from commit 64efe8bf15)
2021-09-27 07:36:13 +00:00
Frappe PR Bot
986ca8815d fix: local variable 'fiscal_year_details' referenced before assignment (#27658) 2021-09-26 20:28:06 +05:30
Frappe PR Bot
646f8b7871 fix: update default KSA VAT rate for setup (#27614) (#27622)
(cherry picked from commit abded895f3)

Co-authored-by: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com>
2021-09-26 11:14:20 +05:30
Frappe PR Bot
08e24bd907 fix: holiday message reminder (#27654)
Minor grammatical change

(cherry picked from commit dafe99b6e2)

Co-authored-by: escix <preminik@preminik.com>
2021-09-26 11:12:48 +05:30
Frappe PR Bot
e5a062ceb6 feat: add Partly Paid status in Invoices (#27636)
(cherry picked from commit c8b9a55e96)

Co-authored-by: Sagar Vora <sagar@resilient.tech>
2021-09-22 12:17:58 +05:30
Bhavesh Maheshwari
9ebabb86b3 fix: remove unknown field employee_name from query (#27634)
* fix: remove unknown field employee_name from query

* fix: remove unknown fieldname

Co-authored-by: root <root@vultr.guest>
Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
2021-09-21 20:30:53 +05:30
Frappe PR Bot
5a8d57f2ae fix: remove bad default for anniversary reminders (#27632) (#27633)
🤦

(cherry picked from commit c302c7ab42)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-21 17:55:05 +05:30
Frappe PR Bot
47c6fba317 fix: (ux) Use subassembly schedule date while making WO from Prod Plan (bp #27628)
- Set subassemply WO's planned start date from Production Plan

(cherry picked from commit 9110223341)

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2021-09-21 17:12:45 +05:30
Frappe PR Bot
19446a8ddd feat: Merge POS invoices based on customer group (#27553)
* feat: Merge POS invoices based on customer group (#27471)

* feat: Merge POS invoices based on customer group

* fix: Linting Issues

* fix: fieldname

Co-authored-by: Saqib <nextchamp.saqib@gmail.com>
(cherry picked from commit c9c8957250)

# Conflicts:
#	erpnext/patches.txt

* fix: Update patches.txt

* fix: Remove v14 patch

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
2021-09-21 11:17:44 +05:30
Frappe PR Bot
fec19a9532 Update training_result.js (#27615) (#27620)
cur_frm is deprecated

(cherry picked from commit 5c400da4e2)

Co-authored-by: François de Ryckel <f.deryckel@gmail.com>
2021-09-21 09:32:18 +05:30
mergify[bot]
6ba1b03ee5 fix: reference row added in allocation table (#27613) 2021-09-20 21:37:55 +05:30
Ganga Manoj
1ba4afa986 fix: Set parent_detail_docname to prevent overwriting Packed/Bundle Items on saving (#27571) 2021-09-20 21:36:07 +05:30
mergify[bot]
10cffe81b0 fix: Webform Permission for custom doctype (backport #26232) (#27592)
* fix: Webform Permission for custom doctype (#26232)

* fix: Webform Permission for custom doctype

* fix: sider fix

* fix: app check condition for getting correct list_context

* chore: Better naming convention

* test: Added test case to check permission for webform of custom doctype

* chore: linting issue

* chore: linting issue

* fix: sider fix

* test: minor changes

* chore: linter and better naming method

* chore: linter fix

(cherry picked from commit 9aa6f52edd)

# Conflicts:
#	erpnext/hooks.py

* chore: Resolved Conflicts

Co-authored-by: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
2021-09-20 20:28:29 +05:30
Ankush Menat
72c22a4219 Merge pull request #27604 from frappe/mergify/bp/version-13-hotfix/pr-27601
fix(ux): warn when overbilling allowance was bypassed due to role (backport #27601)
2021-09-20 18:17:40 +05:30
Ankush Menat
684ca451d3 fix(ux): better error message
Co-authored-by: Saqib <nextchamp.saqib@gmail.com>
(cherry picked from commit 21a955d20b)
2021-09-20 12:13:37 +00:00
Ankush Menat
8cdd6eacdd refactor: add guard clause in for loop
Reduce overly indented code/improve readability.

(cherry picked from commit 5e4fbba753)
2021-09-20 12:13:37 +00:00
Ankush Menat
2dc590c0c0 fix: warn when overbilling checks are skipped.
(cherry picked from commit 43bf82b58b)
2021-09-20 12:13:36 +00:00
Ankush Menat
55393d160d perf: extract loop invariant db calls
(cherry picked from commit 648b2d72a5)
2021-09-20 12:13:36 +00:00
Frappe PR Bot
9b46dd512b fix(plaid): query to check if bank account exists (#27595) 2021-09-20 15:45:13 +05:30
Saqib
7aaecbcac9 fix: Move related fields together in Selling Settings (#27383) 2021-09-20 15:30:50 +05:30
Deepesh Garg
846d128c5d fix: Test Case 2021-09-20 11:06:10 +05:30
Marica
d6ed6d53e9 fix: Handle is_search_module_loaded for redis version < 4.0.0 (#27574)
- Return False if error occurs
2021-09-18 14:23:44 +05:30
vama
666eaae6ce fix: PO/PINV - Check if doctype has company_address field before setting the value (#27441)
Co-authored-by: Vama Mehta <vama.mehta@inqubit.in>
2021-09-18 13:21:45 +05:30
Anoop
9ffb65b5a4 fix(healthcare): Duplicate Contact error on add Patient (#27427)
* fix: duplicate contact error when linking existing Customer to Patient
validation for existing User email and mobile before creating user on Patient update

* test: patient - test contact, user creation

* fix: test_patient_contact clearing contact breaking other tests
sider issues

* fix: use db_set instead of set_value

* fix(test): overlapping appointments

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-09-18 01:02:31 +05:30
Subin Tom
8c01ae952b conflict fixes 2021-09-17 20:18:39 +05:30
Subin Tom
e8cf32e1c8 fixing conflicts 2021-09-17 20:16:10 +05:30
Frappe PR Bot
e6d0a57f0c ci: fix docs checker for empty body (#27569)
Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-17 17:20:59 +05:30
Saqib
5451aed115 Merge branch 'version-13-hotfix' into backport-fix-selling-settings 2021-09-17 15:56:01 +05:30
Frappe PR Bot
6ed40463a0 fix: unecessary keyword args were passed in mapper functions (#27563) (#27565)
(cherry picked from commit e03d9aa889)

Co-authored-by: Saqib <nextchamp.saqib@gmail.com>
2021-09-17 13:11:54 +05:30
Subin Tom
8dfdab9dc1 fix: Tax breakup based on items, missing GST fields (#27524)
* fix: Tax breakup based on items

* fix: added gst fields,warehouse validation to pos inv,patch

* fix: tax breakup test fix, eway bill hsn fix

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
(cherry picked from commit d49346ac45)

# Conflicts:
#	erpnext/patches.txt
#	erpnext/regional/india/setup.py
2021-09-17 05:21:40 +00:00
Frappe PR Bot
e990bb8816 fix: Remove duplicates from customer_code field (bp #27555)
(cherry picked from commit 41f11eca72)

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2021-09-17 05:15:03 +00:00
Deepesh Garg
e2eb72eb5b fix: Deferred revenue entries post account freezing 2021-09-16 18:54:57 +05:30
Frappe PR Bot
450e1e8ed4 fix: no validation on item defaults (#27549)
* fix: no validation on item defaults (#27393)

* fix: no validation on item defaults

* fix: cache value while validating

* test: item default company relation

* fix: reorder validations

* refactor: add guard conditions on update_defaults

* test: add default warehouse for item group

* fix: validate item defaults for item groups

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
(cherry picked from commit 5eba1ccd51)

# Conflicts:
#	erpnext/setup/doctype/item_group/item_group.py
#	erpnext/stock/doctype/item/item.py

* fix: resolve conflicts

Co-authored-by: Saqib <nextchamp.saqib@gmail.com>
Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-16 12:25:39 +00:00
Saqib
24c08f5092 Merge branch 'version-13-hotfix' into backport-fix-selling-settings 2021-09-16 16:59:54 +05:30
Frappe PR Bot
adc5bf7b9c Merge pull request #27543 from frappe-pr-bot/backport/version-13-hotfix/27538
fix: Validate if item exists on uploading items in stock reco
2021-09-16 16:07:56 +05:30
Frappe PR Bot
e9683fad47 fix(ProdPlan): Get SubAssy Items does not work (bp #27537)
* fix(ProdPlan): Get SubAssy Items does not work

This button wasn't working unless the document was saved already.

* fix: make form dirty when subassy item are fetched

(cherry picked from commit 78fe92542c)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-16 10:22:43 +00:00
Frappe PR Bot
5a34520f3f fix(minor): Employee filter in Unpaid Expense Claims report (#27530) (#27531)
(cherry picked from commit 866763c16a)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-09-16 00:14:00 +05:30
Frappe PR Bot
8b3ef8e96e test: automated test for running all stock reports (#27510) (#27522)
* test: automated test for running all stock reports

These test do not assert correctness, they just check that "execute" function
is working with sane filters.

* test: make report execution test modular

(cherry picked from commit 70c203d19e)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-15 22:23:07 +05:30
Frappe PR Bot
0fb112107f fix: not able to submit stock entry with 350 items (#27523) (#27526)
(cherry picked from commit e6a1ad8016)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2021-09-15 21:06:44 +05:30
Deepesh Garg
978028c880 fix(minor): Remove b2c limit check from CDNR Invoices (#27516)
* fix(minor): Remove b2c limit check from CDNR Invoices

* fix: Remove unnecessary format
2021-09-15 18:51:06 +05:30
Frappe PR Bot
4bd1bceee2 fix: table data deleted on submitted maintenance schedule (bp #27513)
(cherry picked from commit 5e0b21582a)

Co-authored-by: Noah Jacob <noahjacobkurian@gmail.com>
2021-09-15 17:45:48 +05:30
Frappe PR Bot
7d733af983 feat: link items to supplier / customer (#27479)
* Merge pull request #27281 from DeeMysterio/party-specific-items

feat: link items to supplier / customer
(cherry picked from commit aa82624f31)

# Conflicts:
#	erpnext/patches.txt

* fix: resolve conflict

Co-authored-by: DeeMysterio <dikshajadhav11.dj@gmail.com>
Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-15 17:19:50 +05:30
Frappe PR Bot
a87cc12024 feat: provision to add scrap item in job card (#27483) (#27512)
(cherry picked from commit c5a77f60ed)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2021-09-15 17:17:59 +05:30
Marica
9e0fb74ab2 Merge pull request #27508 from marination/shopping-cart-fixes
fix: Shopping Cart and Variant Selection
2021-09-15 14:00:14 +05:30
Frappe PR Bot
604f13c69e fix: Values with same account and different account number in consolidated balance sheet report (#27493) (#27504)
(cherry picked from commit 625626b973)

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
2021-09-15 11:49:19 +05:30
Frappe PR Bot
5f4a58fb83 fix: Autoname for customer and supplier (#27505)
* fix: Autoname for customer and supplier (#27398)

(cherry picked from commit 759f2b7920)

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

* fix: Resolve conflicts

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
2021-09-15 11:31:51 +05:30
vama
50fe23308a fix: Tags getting fetched correctly in Get Supplier in RFQ (Request For Quotation) (#27499)
* fix: Tags getting fetched correctly in Get Supplier in RFQ( Request For Quotation ) #26343

* fix: Linting issues

* fix: remove unnecessary caching

[skip ci]

Co-authored-by: Vama Mehta <vama.mehta@inqubit.in>
Co-authored-by: Ankush Menat <ankush@iwebnotes.in>
2021-09-15 11:07:36 +05:30
Frappe PR Bot
247d9bf5c4 fix: Patch for updating tax withholding category dates (#27489) (#27495)
(cherry picked from commit c53b78e712)

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
2021-09-15 10:30:41 +05:30
Frappe PR Bot
e4cbe12a71 fix: calculate operating cost based on BOM Quantity (#27464) (#27501)
* fix: calculate operating cost based on BOM Quantity

* fix: added test cases

(cherry picked from commit 2e2985e4f1)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2021-09-15 01:18:56 +05:30
Marica
2a9fbc609d Merge pull request #27488 from marination/validate-cart-settings
fix: Args missing error on changing Price List currency with cart enabled
2021-09-14 18:43:15 +05:30
Ankush Menat
4081ecfcd7 chore: sort imports 2021-09-14 18:35:17 +05:30
Marica
d76e5dcb93 Merge pull request #27486 from marination/job-card-excess-transfer-hotfix
fix: Handle Excess/Multiple Item Transfer against Job Card
2021-09-14 17:57:39 +05:30
GangaManoj
b26da043c4 fix: Rename section break 2021-09-14 17:07:58 +05:30
Deepesh Garg
d5f4160260 Merge pull request #27481 from deepeshgarg007/gstin_filter_issue_v13
fix: GSTR-1 Reports not showing any data
2021-09-14 16:59:18 +05:30
Deepesh Garg
49fa06f849 Merge pull request #27473 from deepeshgarg007/tds_validity_v13
feat: Validity dates in Tax Withholding Rates
2021-09-14 15:28:52 +05:30
Deepesh Garg
ca2420f921 fix: Update fiscal year 2021-09-14 15:06:35 +05:30
Deepesh Garg
0b07107144 Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into tds_validity_v13 2021-09-14 15:03:30 +05:30
Nabin Hait
10754831c3 fix: Maintain same rate in Stock Ledger until stock become positive (#27227)
* fix: Maintain same rate in Stock Ledger until stock become positive

* fix: Maintain same rate in Stock Ledger until stock become positive
2021-09-14 13:34:36 +05:30
Frappe PR Bot
c9e7e72a29 fix(HR): Ignore invalid fields when updating employee details (#27456) (#27474)
(cherry picked from commit 95460d9818)

Co-authored-by: Chillar Anand <chillar@avilpage.com>
2021-09-14 12:48:31 +05:30
Frappe PR Bot
8fd1aec76c fix: employee advance return through multiple additional salaries (#27470)
* fix: employee advance return through multiple additional salaries (#27438)

* fix: employee advance return through multiple additional salaries

* test: test repay unclaimed amount from salary

* fix: sorting in imports

(cherry picked from commit b98740b44a)

* fix: remove naming rule

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-09-14 12:01:19 +05:30
Deepesh Garg
9708bf3750 fix: Linting Issues 2021-09-14 12:01:02 +05:30
Deepesh Garg
173ae56aec fix: Test Case 2021-09-14 12:01:02 +05:30
Deepesh Garg
f101241907 fix: Hardcode fiscal year and posting date 2021-09-14 12:00:35 +05:30
Deepesh Garg
0bb16b6b59 fix: Debug CI 2021-09-14 12:00:35 +05:30
Deepesh Garg
7d61181f8b test: Update test case 2021-09-14 12:00:09 +05:30
Deepesh Garg
e8edeecc05 fix: Linting and patch fixes 2021-09-14 12:00:09 +05:30
Deepesh Garg
5c9f2bf4a4 fix: Advance TDS test case 2021-09-14 12:00:09 +05:30
Deepesh Garg
dab0fe56f1 feat: Validity dates in Tax Withholding Rates 2021-09-14 12:00:08 +05:30
Frappe PR Bot
52a99d8da6 fix: editable price list rate field in sales transactions (#27455) (#27461)
(cherry picked from commit a5baf909b7)

Co-authored-by: Saqib <nextchamp.saqib@gmail.com>
2021-09-13 18:48:05 +05:30
Frappe PR Bot
64796d3029 fix(Payroll): incorrect component amount calculation if dependent on another payment days based component (#27453)
* fix(Payroll): incorrect component amount calculation if dependent on another payment days based component (#27349)

* fix(Payroll): incorrect component amount calculation if dependent on another payment days based component

* fix: set component amount precision at the end

* fix: consider default amount during taxt calculations

* test: component amount dependent on another payment days based component

* fix: test

(cherry picked from commit bab644a249)

# Conflicts:
#	erpnext/payroll/doctype/salary_slip/test_salary_slip.py

* fix: conflicts in test file

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2021-09-13 15:13:43 +05:30
Saqib
bb8d90d741 feat: (get_items_from) filter material request item in purchase order (#24725) 2021-09-13 11:46:00 +05:30
Ankush Menat
5c1f0c98f8 Revert "fix: Salary component account filter (#26605)" (#27446)
This reverts commit aaea5edbdb.
2021-09-13 10:09:01 +05:30
Frappe PR Bot
00c4e346c7 fix(ux): clean invalid fields from variant setting (#27442)
* fix(ux): clean invalid fields from variant setting (#27412)

(cherry picked from commit 6ef879fca2)

# Conflicts:
#	erpnext/stock/doctype/item_variant_settings/item_variant_settings.js

* fix: resolve conflicts

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-12 16:41:31 +05:30
Frappe PR Bot
74fa6fab4c fix(ux): apply proper filtering in stock reports (#27411) (#27443)
* fix(ux): apply proper filtering in stock reports

Stock Balance: apply company filter to warehouse field
Stock Ageing: apply company filter to warehouse field

* fix: unnecessary parens

Co-authored-by: Alan <2.alan.tom@gmail.com>
(cherry picked from commit d743c41b54)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-12 16:41:17 +05:30
Sagar Vora
dd352df168 Merge pull request #27437 from frappe-pr-bot/backport/version-13-hotfix/27433
fix: Template Error due to use of single quote
2021-09-11 17:59:03 +05:30
Sagar Vora
0ecaf23722 fix: Template Error due to use of single quote (#27433)
(cherry picked from commit dae0a1c1d6)
2021-09-11 12:25:27 +00:00
Devin Slauenwhite
becf471a3a fix: fail migration due to None type during v13_0.update_returned_qty_in_pr_dn (#27430)
* fix: fail migration due to None type

* fix: incorrect key: value pair in filter.

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-11 12:22:49 +00:00
Frappe PR Bot
8337ec9fe0 test: basic tests for controllers/queries (bp #27422)
(cherry picked from commit 62fc544280)

Co-authored-by: Ankush Menat <ankush@iwebnotes.com>
2021-09-10 13:09:16 +05:30
Ankush Menat
a33de1bdfe chore: remove snyk from dev-dependencies (#27425) 2021-09-10 13:08:06 +05:30
Ganga Manoj
7dce900ff5 Merge branch 'version-13-hotfix' into backport-fix-selling-settings 2021-09-07 20:51:47 +05:30
GangaManoj
d623eac084 fix: Move related fields to the same section 2021-09-07 17:32:01 +05:30
GangaManoj
c128618185 fix: Rename 'Action if Same Rate is Not Maintained' to 'Action if Same Rate is Not Maintained Throughout Sales Cycle' 2021-09-07 17:05:20 +05:30
GangaManoj
d67460bf22 fix: Only display 'Role Allowed to Override Stop Action' if 'Maintain Same Rate Throughout Sales Cycle' is checked 2021-09-07 17:04:29 +05:30
GangaManoj
7f1d611b47 fix: Remove redundant description 2021-09-07 17:04:04 +05:30
328 changed files with 7227 additions and 6078 deletions

View File

@@ -1,6 +1,8 @@
[flake8]
ignore =
B007,
B009,
B010,
B950,
E101,
E111,

View File

@@ -34,9 +34,9 @@ if __name__ == "__main__":
if response.ok:
payload = response.json()
title = payload.get("title", "").lower().strip()
head_sha = payload.get("head", {}).get("sha")
body = payload.get("body", "").lower()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (title.startswith("feat")
and head_sha

View File

@@ -131,3 +131,21 @@ rules:
key `$X` is uselessly assigned twice. This could be a potential bug.
languages: [python]
severity: ERROR
- id: frappe-manual-commit
patterns:
- pattern: frappe.db.commit()
- pattern-not-inside: |
try:
...
except ...:
...
message: |
Manually commiting a transaction is highly discouraged. Read about the transaction model implemented by Frappe Framework before adding manual commits: https://frappeframework.com/docs/user/en/api/database#database-transaction-model If you think manual commit is required then add a comment explaining why and `// nosemgrep` on the same line.
paths:
exclude:
- "**/patches/**"
- "**/demo/**"
languages: [python]
severity: ERROR

View File

@@ -99,6 +99,8 @@ jobs:
- name: Build Assets
run: cd ~/frappe-bench/ && bench build
env:
CI: Yes
- name: UI Tests
run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests erpnext --headless

58
.mergify.yml Normal file
View File

@@ -0,0 +1,58 @@
pull_request_rules:
- name: Auto-close PRs on stable branch
conditions:
- and:
- and:
- author!=surajshetty3416
- author!=gavindsouza
- author!=rohitwaghchaure
- author!=nabinhait
- or:
- base=version-13
- base=version-12
actions:
close:
comment:
message: |
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
- name: backport to version-13-hotfix
conditions:
- label="backport version-13-hotfix"
actions:
backport:
branches:
- version-13-hotfix
assignees:
- "{{ author }}"
- name: backport to version-13-pre-release
conditions:
- label="backport version-13-pre-release"
actions:
backport:
branches:
- version-13-pre-release
assignees:
- "{{ author }}"
- name: backport to version-12-hotfix
conditions:
- label="backport version-12-hotfix"
actions:
backport:
branches:
- version-12-hotfix
assignees:
- "{{ author }}"
- name: backport to version-12-pre-release
conditions:
- label="backport version-12-pre-release"
actions:
backport:
branches:
- version-12-pre-release
assignees:
- "{{ author }}"

View File

@@ -20,6 +20,9 @@ repos:
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear',
]
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"

8
.snyk
View File

@@ -1,8 +0,0 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.14.0
ignore: {}
# patches apply the minimum changes required to fix a vulnerability
patch:
SNYK-JS-LODASH-450202:
- cypress > getos > async > lodash:
patched: '2020-01-31T01:35:12.802Z'

View File

@@ -6,7 +6,7 @@ context('Organizational Chart', () => {
it('navigates to org chart', () => {
cy.visit('/app');
cy.awesomebar('Organizational Chart');
cy.visit('/app/organizational-chart');
cy.url().should('include', '/organizational-chart');
cy.window().its('frappe.csrf_token').then(csrf_token => {
@@ -24,7 +24,7 @@ context('Organizational Chart', () => {
cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
cy.get('@input')
.clear({ force: true })
.type('Test Org Chart{enter}', { force: true })
.type('Test Org Chart{downarrow}{enter}', { force: true })
.blur({ force: true });
});
});

View File

@@ -7,7 +7,7 @@ context('Organizational Chart Mobile', () => {
it('navigates to org chart', () => {
cy.viewport(375, 667);
cy.visit('/app');
cy.awesomebar('Organizational Chart');
cy.visit('/app/organizational-chart');
cy.url().should('include', '/organizational-chart');
cy.window().its('frappe.csrf_token').then(csrf_token => {
@@ -25,7 +25,7 @@ context('Organizational Chart Mobile', () => {
cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
cy.get('@input')
.clear({ force: true })
.type('Test Org Chart{enter}', { force: true })
.type('Test Org Chart{downarrow}{enter}', { force: true })
.blur({ force: true });
});
});

View File

@@ -7,7 +7,7 @@ import frappe
from erpnext.hooks import regional_overrides
__version__ = '13.11.1'
__version__ = '13.13.0'
def get_default_company(user=None):
'''Get default company for user'''

View File

@@ -374,12 +374,15 @@ def make_gl_entries(doc, credit_account, debit_account, against,
try:
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
frappe.db.commit()
except Exception:
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(message=traceback)
except Exception as e:
if frappe.flags.in_test:
raise e
else:
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(message=traceback)
frappe.flags.deferred_accounting_error = True
frappe.flags.deferred_accounting_error = True
def send_mail(deferred_process):
title = _("Error while processing deferred accounting for {0}").format(deferred_process)

View File

@@ -8,6 +8,8 @@ from frappe import _, throw
from frappe.utils import cint, cstr
from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of
import erpnext
class RootNotEditable(frappe.ValidationError): pass
class BalanceMismatchError(frappe.ValidationError): pass
@@ -196,7 +198,7 @@ class Account(NestedSet):
"company": company,
# parent account's currency should be passed down to child account's curreny
# if it is None, it picks it up from default company currency, which might be unintended
"account_currency": self.account_currency,
"account_currency": erpnext.get_company_currency(company),
"parent_account": parent_acc_name_map[company]
})
@@ -207,8 +209,7 @@ class Account(NestedSet):
# update the parent company's value in child companies
doc = frappe.get_doc("Account", child_account)
parent_value_changed = False
for field in ['account_type', 'account_currency',
'freeze_account', 'balance_must_be']:
for field in ['account_type', 'freeze_account', 'balance_must_be']:
if doc.get(field) != self.get(field):
parent_value_changed = True
doc.set(field, self.get(field))

View File

@@ -45,6 +45,49 @@ frappe.treeview_settings["Account"] = {
],
root_label: "Accounts",
get_tree_nodes: 'erpnext.accounts.utils.get_children',
on_get_node: function(nodes, deep=false) {
if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return;
let accounts = [];
if (deep) {
// in case of `get_all_nodes`
accounts = nodes.reduce((acc, node) => [...acc, ...node.data], []);
} else {
accounts = nodes;
}
const get_balances = frappe.call({
method: 'erpnext.accounts.utils.get_account_balances',
args: {
accounts: accounts,
company: cur_tree.args.company
},
});
get_balances.then(r => {
if (!r.message || r.message.length == 0) return;
for (let account of r.message) {
const node = cur_tree.nodes && cur_tree.nodes[account.value];
if (!node || node.is_root) continue;
// show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? "Dr": "Cr";
const format = (value, currency) => format_currency(Math.abs(value), currency);
if (account.balance!==undefined) {
$('<span class="balance-area pull-right">'
+ (account.balance_in_account_currency ?
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
+ format(account.balance, account.company_currency)
+ " " + dr_or_cr
+ '</span>').insertBefore(node.$ul);
}
}
});
},
add_tree_node: 'erpnext.accounts.utils.add_ac',
menu_items:[
{
@@ -122,24 +165,6 @@ frappe.treeview_settings["Account"] = {
}
}, "add");
},
onrender: function(node) {
if (frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
// show Dr if positive since balance is calculated as debit - credit else show Cr
let balance = node.data.balance_in_account_currency || node.data.balance;
let dr_or_cr = balance > 0 ? "Dr": "Cr";
if (node.data && node.data.balance!==undefined) {
$('<span class="balance-area pull-right">'
+ (node.data.balance_in_account_currency ?
(format_currency(Math.abs(node.data.balance_in_account_currency),
node.data.account_currency) + " / ") : "")
+ format_currency(Math.abs(node.data.balance), node.data.company_currency)
+ " " + dr_or_cr
+ '</span>').insertBefore(node.$ul);
}
}
},
toolbar: [
{
label:__("Add Child"),

View File

@@ -12,7 +12,7 @@ from six import iteritems
from unidecode import unidecode
def create_charts(company, chart_template=None, existing_company=None, custom_chart=None):
def create_charts(company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None):
chart = custom_chart or get_chart(chart_template, existing_company)
if chart:
accounts = []
@@ -22,7 +22,7 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch
if root_account:
root_type = child.get("root_type")
if account_name not in ["account_number", "account_type",
if account_name not in ["account_name", "account_number", "account_type",
"root_type", "is_group", "tax_rate"]:
account_number = cstr(child.get("account_number")).strip()
@@ -35,7 +35,7 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch
account = frappe.get_doc({
"doctype": "Account",
"account_name": account_name,
"account_name": child.get('account_name') if from_coa_importer else account_name,
"company": company,
"parent_account": parent,
"is_group": is_group,
@@ -213,7 +213,7 @@ def validate_bank_account(coa, bank_account):
return (bank_account in accounts)
@frappe.whitelist()
def build_tree_from_json(chart_template, chart_data=None):
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False):
''' get chart template from its folder and parse the json to be rendered as tree '''
chart = chart_data or get_chart(chart_template)
@@ -226,9 +226,12 @@ def build_tree_from_json(chart_template, chart_data=None):
''' recursively called to form a parent-child based list of dict from chart template '''
for account_name, child in iteritems(children):
account = {}
if account_name in ["account_number", "account_type",\
if account_name in ["account_name", "account_number", "account_type",\
"root_type", "is_group", "tax_rate"]: continue
if from_coa_importer:
account_name = child['account_name']
account['parent_account'] = parent
account['expandable'] = True if identify_is_group(child) else False
account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \

View File

@@ -175,7 +175,7 @@
"default": "0",
"fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check",
"label": "Automatically Fetch Payment Terms"
"label": "Automatically Fetch Payment Terms from Order"
},
{
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
@@ -283,7 +283,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-08-19 11:17:38.788054",
"modified": "2021-10-11 17:42:36.427699",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -10,13 +10,17 @@ frappe.ui.form.on('Chart of Accounts Importer', {
// make company mandatory
frm.set_df_property('company', 'reqd', frm.doc.company ? 0 : 1);
frm.set_df_property('import_file_section', 'hidden', frm.doc.company ? 0 : 1);
if (frm.doc.import_file) {
frappe.run_serially([
() => generate_tree_preview(frm),
() => create_import_button(frm),
() => frm.set_df_property('chart_preview', 'hidden', 0)
]);
}
frm.set_df_property('chart_preview', 'hidden',
$(frm.fields_dict['chart_tree'].wrapper).html()!="" ? 0 : 1);
// Show import button when file is successfully attached
if (frm.page && frm.page.show_import_button) {
create_import_button(frm);
}
},
download_template: function(frm) {
@@ -77,9 +81,6 @@ frappe.ui.form.on('Chart of Accounts Importer', {
if (!frm.doc.import_file) {
frm.page.set_indicator("");
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file
} else {
generate_tree_preview(frm);
validate_csv_data(frm);
}
},
@@ -104,26 +105,9 @@ frappe.ui.form.on('Chart of Accounts Importer', {
}
});
var validate_csv_data = function(frm) {
frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_accounts",
args: {file_name: frm.doc.import_file},
callback: function(r) {
if(r.message && r.message[0]===true) {
frm.page["show_import_button"] = true;
frm.page["total_accounts"] = r.message[1];
frm.trigger("refresh");
} else {
frm.page.set_indicator(__('Resolve error and upload again.'), 'orange');
frappe.throw(__(r.message));
}
}
});
};
var create_import_button = function(frm) {
frm.page.set_primary_action(__("Import"), function () {
frappe.call({
return frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa",
args: {
file_name: frm.doc.import_file,
@@ -132,7 +116,7 @@ var create_import_button = function(frm) {
freeze: true,
freeze_message: __("Creating Accounts..."),
callback: function(r) {
if(!r.exc) {
if (!r.exc) {
clearInterval(frm.page["interval"]);
frm.page.set_indicator(__('Import Successful'), 'blue');
create_reset_button(frm);
@@ -150,12 +134,33 @@ var create_reset_button = function(frm) {
}).addClass('btn btn-primary');
};
var validate_coa = function(frm) {
if (frm.doc.import_file) {
let parent = __('All Accounts');
return frappe.call({
'method': 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
'args': {
file_name: frm.doc.import_file,
parent: parent,
doctype: 'Chart of Accounts Importer',
file_type: frm.doc.file_type,
for_validate: 1
},
callback: function(r) {
if (r.message['show_import_button']) {
frm.page['show_import_button'] = Boolean(r.message['show_import_button']);
}
}
});
}
};
var generate_tree_preview = function(frm) {
let parent = __('All Accounts');
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data
// generate tree structure based on the csv data
new frappe.ui.Tree({
return new frappe.ui.Tree({
parent: $(frm.fields_dict['chart_tree'].wrapper),
label: parent,
expandable: true,

View File

@@ -26,7 +26,18 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
class ChartofAccountsImporter(Document):
def validate(self):
validate_accounts(self.import_file)
if self.import_file:
get_coa('Chart of Accounts Importer', 'All Accounts', file_name=self.import_file, for_validate=1)
def validate_columns(data):
if not data:
frappe.throw(_('No data found. Seems like you uploaded a blank file'))
no_of_columns = max([len(d) for d in data])
if no_of_columns > 7:
frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template'),
title=(_("Wrong Template")))
@frappe.whitelist()
def validate_company(company):
@@ -56,8 +67,9 @@ def import_coa(file_name, company):
else:
data = generate_data_from_excel(file_doc, extension)
frappe.local.flags.ignore_root_company_validation = True
forest = build_forest(data)
create_charts(company, custom_chart=forest)
create_charts(company, custom_chart=forest, from_coa_importer=True)
# trigger on_update for company to reset default accounts
set_default_accounts(company)
@@ -120,7 +132,7 @@ def generate_data_from_excel(file_doc, extension, as_dict=False):
return data
@frappe.whitelist()
def get_coa(doctype, parent, is_root=False, file_name=None):
def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0):
''' called by tree view (to fetch node's children) '''
file_doc, extension = get_file(file_name)
@@ -131,13 +143,21 @@ def get_coa(doctype, parent, is_root=False, file_name=None):
else:
data = generate_data_from_excel(file_doc, extension)
forest = build_forest(data)
accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form
validate_columns(data)
validate_accounts(file_doc, extension)
# filter out to show data for the selected node only
accounts = [d for d in accounts if d['parent_account']==parent]
if not for_validate:
forest = build_forest(data)
accounts = build_tree_from_json("", chart_data=forest, from_coa_importer=True) # returns a list of dict in a tree render-able form
return accounts
# filter out to show data for the selected node only
accounts = [d for d in accounts if d['parent_account']==parent]
return accounts
else:
return {
'show_import_button': 1
}
def build_forest(data):
'''
@@ -192,11 +212,14 @@ def build_forest(data):
if not account_name:
error_messages.append("Row {0}: Please enter Account Name".format(line_no))
name = account_name
if account_number:
account_number = cstr(account_number).strip()
account_name = "{} - {}".format(account_number, account_name)
charts_map[account_name] = {}
charts_map[account_name]['account_name'] = name
if account_number: charts_map[account_name]["account_number"] = account_number
if cint(is_group) == 1: charts_map[account_name]["is_group"] = is_group
if account_type: charts_map[account_name]["account_type"] = account_type
if root_type: charts_map[account_name]["root_type"] = root_type
@@ -294,10 +317,7 @@ def get_sample_template(writer):
@frappe.whitelist()
def validate_accounts(file_name):
file_doc, extension = get_file(file_name)
def validate_accounts(file_doc, extension):
if extension == 'csv':
accounts = generate_data_from_csv(file_doc, as_dict=True)
else:
@@ -316,15 +336,10 @@ def validate_accounts(file_name):
validate_root(accounts_dict)
validate_account_types(accounts_dict)
return [True, len(accounts)]
def validate_root(accounts):
roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
if len(roots) < 4:
frappe.throw(_("Number of root accounts cannot be less than 4"))
error_messages = []
for account in roots:
@@ -333,9 +348,19 @@ def validate_root(accounts):
elif account.get("root_type") not in get_root_types() and account.get("account_name"):
error_messages.append(_("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(account.get("account_name")))
validate_missing_roots(roots)
if error_messages:
frappe.throw("<br>".join(error_messages))
def validate_missing_roots(roots):
root_types_added = set(d.get('root_type') for d in roots)
missing = list(set(get_root_types()) - root_types_added)
if missing:
frappe.throw(_("Please add Root Account for - {0}").format(' , '.join(missing)))
def get_root_types():
return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
@@ -361,23 +386,6 @@ def get_mandatory_account_types():
{'account_type': 'Stock', 'root_type': 'Asset'}
]
def validate_account_types(accounts):
account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"]
account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1]
missing = list(set(account_types_for_ledger) - set(account_types))
if missing:
frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)))
account_types_for_group = ["Bank", "Cash", "Stock"]
# fix logic bug
account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1]
missing = list(set(account_types_for_group) - set(account_groups))
if missing:
frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing)))
def unset_existing_data(company):
linked = frappe.db.sql('''select fieldname from tabDocField
where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True)

View File

@@ -1,63 +1,33 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2014-10-02 13:35:44.155278",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"actions": [],
"creation": "2014-10-02 13:35:44.155278",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company"
],
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"fieldname": "company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Company",
"options": "Company"
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:00.505946",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Fiscal Year Company",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_seen": 0
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-28 18:01:53.495929",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Fiscal Year Company",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -13,10 +13,12 @@
"voucher_type",
"naming_series",
"finance_book",
"tax_withholding_category",
"column_break1",
"from_template",
"company",
"posting_date",
"apply_tds",
"2_add_edit_gl_entries",
"accounts",
"section_break99",
@@ -498,16 +500,32 @@
"options": "Journal Entry Template",
"print_hide": 1,
"report_hide": 1
},
{
"depends_on": "eval:doc.apply_tds",
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
"label": "Tax Withholding Category",
"mandatory_depends_on": "eval:doc.apply_tds",
"options": "Tax Withholding Category"
},
{
"default": "0",
"depends_on": "eval:['Credit Note', 'Debit Note'].includes(doc.voucher_type)",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply Tax Withholding Amount "
}
],
"icon": "fa fa-file-text",
"idx": 176,
"is_submittable": 1,
"links": [],
"modified": "2020-10-30 13:56:01.121995",
"modified": "2021-09-09 15:31:14.484029",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{

View File

@@ -15,6 +15,9 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
get_party_account_based_on_invoice_discounting,
)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
from erpnext.accounts.party import get_party_account
from erpnext.accounts.utils import (
check_if_stock_and_account_balance_synced,
@@ -57,7 +60,8 @@ class JournalEntry(AccountsController):
self.validate_against_jv()
self.validate_reference_doc()
self.set_against_account()
if self.docstatus == 0:
self.set_against_account()
self.create_remarks()
self.set_print_format_fields()
self.validate_expense_claim()
@@ -66,6 +70,10 @@ class JournalEntry(AccountsController):
self.set_account_and_party_balance()
self.validate_inter_company_accounts()
self.validate_stock_accounts()
if self.docstatus == 0:
self.apply_tax_withholding()
if not self.title:
self.title = self.get_title()
@@ -128,6 +136,72 @@ class JournalEntry(AccountsController):
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
.format(account), StockAccountInvalidTransaction)
def apply_tax_withholding(self):
from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map
if not self.apply_tds or self.voucher_type not in ('Debit Note', 'Credit Note'):
return
parties = [d.party for d in self.get('accounts') if d.party]
parties = list(set(parties))
if len(parties) > 1:
frappe.throw(_("Cannot apply TDS against multiple parties in one entry"))
account_type_map = get_account_type_map(self.company)
party_type = 'supplier' if self.voucher_type == 'Credit Note' else 'customer'
doctype = 'Purchase Invoice' if self.voucher_type == 'Credit Note' else 'Sales Invoice'
debit_or_credit = 'debit_in_account_currency' if self.voucher_type == 'Credit Note' else 'credit_in_account_currency'
rev_debit_or_credit = 'credit_in_account_currency' if debit_or_credit == 'debit_in_account_currency' else 'debit_in_account_currency'
party_account = get_party_account(party_type.title(), parties[0], self.company)
net_total = sum(d.get(debit_or_credit) for d in self.get('accounts') if account_type_map.get(d.account)
not in ('Tax', 'Chargeable'))
party_amount = sum(d.get(rev_debit_or_credit) for d in self.get('accounts') if d.account == party_account)
inv = frappe._dict({
party_type: parties[0],
'doctype': doctype,
'company': self.company,
'posting_date': self.posting_date,
'net_total': net_total
})
tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category)
if not tax_withholding_details:
return
accounts = []
for d in self.get('accounts'):
if d.get('account') == tax_withholding_details.get("account_head"):
d.update({
'account': tax_withholding_details.get("account_head"),
debit_or_credit: tax_withholding_details.get('tax_amount')
})
accounts.append(d.get('account'))
if d.get('account') == party_account:
d.update({
rev_debit_or_credit: party_amount - tax_withholding_details.get('tax_amount')
})
if not accounts or tax_withholding_details.get("account_head") not in accounts:
self.append("accounts", {
'account': tax_withholding_details.get("account_head"),
rev_debit_or_credit: tax_withholding_details.get('tax_amount'),
'against_account': parties[0]
})
to_remove = [d for d in self.get('accounts')
if not d.get(rev_debit_or_credit) and d.account == tax_withholding_details.get("account_head")]
for d in to_remove:
self.remove(d)
def update_inter_company_jv(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
frappe.db.set_value("Journal Entry", self.inter_company_journal_entry_reference,\

View File

@@ -16,7 +16,7 @@ class LoyaltyPointEntry(Document):
def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=None):
if not expiry_date:
date = today()
expiry_date = today()
return frappe.db.sql('''
select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice

View File

@@ -390,6 +390,9 @@ class PaymentEntry(AccountsController):
invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100)
for key, allocated_amount in iteritems(invoice_payment_amount_map):
if not invoice_paid_amount_map.get(key):
frappe.throw(_('Payment term {0} not used in {1}').format(key[0], key[1]))
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get('discounted_amt'))
@@ -502,12 +505,13 @@ class PaymentEntry(AccountsController):
def validate_received_amount(self):
if self.paid_from_account_currency == self.paid_to_account_currency:
if self.paid_amount != self.received_amount:
frappe.throw(_("Received Amount should be same as Paid Amount"))
if self.paid_amount < self.received_amount:
frappe.throw(_("Received Amount cannot be greater than Paid Amount"))
def set_received_amount(self):
self.base_received_amount = self.base_paid_amount
if self.paid_from_account_currency == self.paid_to_account_currency:
if self.paid_from_account_currency == self.paid_to_account_currency \
and not self.payment_type == 'Internal Transfer':
self.received_amount = self.paid_amount
def set_amounts_after_tax(self):
@@ -709,10 +713,14 @@ class PaymentEntry(AccountsController):
dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit"
for d in self.get("references"):
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
gle = party_gl_dict.copy()
gle.update({
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name
"against_voucher": d.reference_name,
"cost_center": cost_center
})
allocated_amount_in_company_currency = flt(flt(d.allocated_amount) * flt(d.exchange_rate),

View File

@@ -93,6 +93,7 @@
"options": "Payment Term"
},
{
"depends_on": "exchange_gain_loss",
"fieldname": "exchange_gain_loss",
"fieldtype": "Currency",
"label": "Exchange Gain/Loss",
@@ -103,7 +104,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-04-21 13:30:11.605388",
"modified": "2021-09-26 17:06:55.597389",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",

View File

@@ -52,21 +52,35 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
refresh: function() {
this.frm.disable_save();
this.frm.set_df_property('invoices', 'cannot_delete_rows', true);
this.frm.set_df_property('payments', 'cannot_delete_rows', true);
this.frm.set_df_property('allocation', 'cannot_delete_rows', true);
this.frm.set_df_property('invoices', 'cannot_add_rows', true);
this.frm.set_df_property('payments', 'cannot_add_rows', true);
this.frm.set_df_property('allocation', 'cannot_add_rows', true);
if (this.frm.doc.receivable_payable_account) {
this.frm.add_custom_button(__('Get Unreconciled Entries'), () =>
this.frm.trigger("get_unreconciled_entries")
);
this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
}
if (this.frm.doc.invoices.length && this.frm.doc.payments.length) {
this.frm.add_custom_button(__('Allocate'), () =>
this.frm.trigger("allocate")
);
this.frm.change_custom_button_type('Allocate', null, 'primary');
this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default');
}
if (this.frm.doc.allocation.length) {
this.frm.add_custom_button(__('Reconcile'), () =>
this.frm.trigger("reconcile")
);
this.frm.change_custom_button_type('Reconcile', null, 'primary');
this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default');
this.frm.change_custom_button_type('Allocate', null, 'default');
}
},

View File

@@ -12,15 +12,16 @@
"receivable_payable_account",
"col_break1",
"from_invoice_date",
"to_invoice_date",
"minimum_invoice_amount",
"maximum_invoice_amount",
"invoice_limit",
"column_break_13",
"from_payment_date",
"to_payment_date",
"minimum_invoice_amount",
"minimum_payment_amount",
"column_break_11",
"to_invoice_date",
"to_payment_date",
"maximum_invoice_amount",
"maximum_payment_amount",
"column_break_13",
"invoice_limit",
"payment_limit",
"bank_cash_account",
"sec_break1",
@@ -79,6 +80,7 @@
},
{
"depends_on": "eval:(doc.payments).length || (doc.invoices).length",
"description": "If you need to reconcile particular transactions against each other, then please select accordingly. If not, all the transactions will be allocated in FIFO order.",
"fieldname": "sec_break1",
"fieldtype": "Section Break",
"label": "Unreconciled Entries"
@@ -163,6 +165,7 @@
"label": "Maximum Payment Amount"
},
{
"description": "System will fetch all the entries if limit value is zero.",
"fieldname": "payment_limit",
"fieldtype": "Int",
"label": "Payment Limit"
@@ -171,13 +174,17 @@
"fieldname": "maximum_invoice_amount",
"fieldtype": "Currency",
"label": "Maximum Invoice Amount"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
}
],
"hide_toolbar": 1,
"icon": "icon-resize-horizontal",
"issingle": 1,
"links": [],
"modified": "2021-08-30 13:05:51.977861",
"modified": "2021-10-04 20:27:11.114194",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",

View File

@@ -7,14 +7,15 @@
"field_order": [
"reference_type",
"reference_name",
"reference_row",
"column_break_3",
"invoice_type",
"invoice_number",
"section_break_6",
"allocated_amount",
"unreconciled_amount",
"amount",
"column_break_8",
"amount",
"is_advance",
"section_break_5",
"difference_amount",
@@ -121,11 +122,18 @@
"label": "Amount",
"options": "Currency",
"read_only": 1
},
{
"fieldname": "reference_row",
"fieldtype": "Data",
"hidden": 1,
"label": "Reference Row",
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2021-08-30 10:58:42.665107",
"modified": "2021-10-06 11:48:59.616562",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Allocation",

View File

@@ -40,6 +40,7 @@ class POSInvoice(SalesInvoice):
self.validate_change_amount()
self.validate_change_account()
self.validate_item_cost_centers()
self.validate_warehouse()
self.validate_serialised_or_batched_item()
self.validate_stock_availablility()
self.validate_return_items_qty()

View File

@@ -4,7 +4,7 @@
frappe.ui.form.on('POS Invoice Merge Log', {
setup: function(frm) {
frm.set_query("pos_invoice", "pos_invoices", doc => {
return{
return {
filters: {
'docstatus': 1,
'customer': doc.customer,
@@ -12,5 +12,10 @@ frappe.ui.form.on('POS Invoice Merge Log', {
}
}
});
},
merge_invoices_based_on: function(frm) {
frm.set_value('customer', '');
frm.set_value('customer_group', '');
}
});

View File

@@ -6,9 +6,11 @@
"engine": "InnoDB",
"field_order": [
"posting_date",
"customer",
"merge_invoices_based_on",
"column_break_3",
"pos_closing_entry",
"customer",
"customer_group",
"section_break_3",
"pos_invoices",
"references_section",
@@ -88,12 +90,27 @@
"fieldtype": "Link",
"label": "POS Closing Entry",
"options": "POS Closing Entry"
},
{
"fieldname": "merge_invoices_based_on",
"fieldtype": "Select",
"label": "Merge Invoices Based On",
"options": "Customer\nCustomer Group",
"reqd": 1
},
{
"depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'",
"fieldname": "customer_group",
"fieldtype": "Link",
"label": "Customer Group",
"mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'",
"options": "Customer Group"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-12-01 11:53:57.267579",
"modified": "2021-09-14 11:17:19.001142",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Merge Log",

View File

@@ -23,6 +23,9 @@ class POSInvoiceMergeLog(Document):
self.validate_pos_invoice_status()
def validate_customer(self):
if self.merge_invoices_based_on == 'Customer Group':
return
for d in self.pos_invoices:
if d.customer != self.customer:
frappe.throw(_("Row #{}: POS Invoice {} is not against customer {}").format(d.idx, d.pos_invoice, self.customer))
@@ -124,7 +127,7 @@ class POSInvoiceMergeLog(Document):
found = False
for i in items:
if (i.item_code == item.item_code and not i.serial_no and not i.batch_no and
i.uom == item.uom and i.net_rate == item.net_rate):
i.uom == item.uom and i.net_rate == item.net_rate and i.warehouse == item.warehouse):
found = True
i.qty = i.qty + item.qty
@@ -172,6 +175,11 @@ class POSInvoiceMergeLog(Document):
invoice.discount_amount = 0.0
invoice.taxes_and_charges = None
invoice.ignore_pricing_rule = 1
invoice.customer = self.customer
if self.merge_invoices_based_on == 'Customer Group':
invoice.flags.ignore_pos_profile = True
invoice.pos_profile = ''
return invoice
@@ -228,7 +236,7 @@ def get_all_unconsolidated_invoices():
return pos_invoices
def get_invoice_customer_map(pos_invoices):
# pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] }
# pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Customer 2' : [{}] }
pos_invoice_customer_map = {}
for invoice in pos_invoices:
customer = invoice.get('customer')

View File

@@ -33,7 +33,9 @@ class TestPOSProfile(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Profile`")
def get_customers_list(pos_profile={}):
def get_customers_list(pos_profile=None):
if pos_profile is None:
pos_profile = {}
cond = "1=1"
customer_groups = []
if pos_profile.get('customer_groups'):

View File

@@ -398,7 +398,9 @@ def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules):
pricing_rules[0].apply_rule_on_other_items = items
return pricing_rules
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
if items is None:
items = []
sum_qty, sum_amt = [0, 0]
doctype = doc.get('parenttype') or doc.doctype

View File

@@ -69,7 +69,9 @@ class PromotionalScheme(Document):
{'promotional_scheme': self.name}):
frappe.delete_doc('Pricing Rule', rule.name)
def get_pricing_rules(doc, rules = {}):
def get_pricing_rules(doc, rules=None):
if rules is None:
rules = {}
new_doc = []
for child_doc, fields in {'price_discount_slabs': price_discount_fields,
'product_discount_slabs': product_discount_fields}.items():
@@ -78,7 +80,9 @@ def get_pricing_rules(doc, rules = {}):
return new_doc
def _get_pricing_rules(doc, child_doc, discount_fields, rules = {}):
def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
if rules is None:
rules = {}
new_doc = []
args = get_args_for_pricing_rule(doc)
applicable_for = frappe.scrub(doc.get('applicable_for'))

View File

@@ -15,6 +15,8 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
check_if_return_invoice_linked_with_payment_entry,
get_total_in_party_account_currency,
is_overdue,
unlink_inter_company_doc,
update_linked_doc,
validate_inter_company_party,
@@ -1109,6 +1111,12 @@ class PurchaseInvoice(BuyingController):
if not self.apply_tds:
return
if self.apply_tds and not self.get('tax_withholding_category'):
self.tax_withholding_category = frappe.db.get_value('Supplier', self.supplier, 'tax_withholding_category')
if not self.tax_withholding_category:
return
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
if not tax_withholding_details:
@@ -1139,10 +1147,8 @@ class PurchaseInvoice(BuyingController):
self.status = 'Draft'
return
precision = self.precision("outstanding_amount")
outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
total = get_total_in_party_account_currency(self)
if not status:
if self.docstatus == 2:
@@ -1150,9 +1156,11 @@ class PurchaseInvoice(BuyingController):
elif self.docstatus == 1:
if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate:
elif is_overdue(self, total):
self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate:
elif 0 < outstanding_amount < total:
self.status = "Partly Paid"
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
self.status = "Unpaid"
#Check if outstanding amount is 0 due to debit note issued against invoice
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):

View File

@@ -2,28 +2,58 @@
// License: GNU General Public License v3. See license.txt
// render
frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"],
get_indicator: function(doc) {
if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
} else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "darkgrey"];
} else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
return [__("Temporarily on Hold"), "darkgrey"];
} else if (frappe.datetime.get_diff(doc.due_date) < 0) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
} else {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
}
} else if (cint(doc.is_return)) {
return [__("Return"), "gray", "is_return,=,Yes"];
} else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"];
} else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
return [__("Paid"), "green", "outstanding_amount,=,0"];
frappe.listview_settings["Purchase Invoice"] = {
add_fields: [
"supplier",
"supplier_name",
"base_grand_total",
"outstanding_amount",
"due_date",
"company",
"currency",
"is_return",
"release_date",
"on_hold",
"represents_company",
"is_internal_supplier",
],
get_indicator(doc) {
if (doc.status == "Debit Note Issued") {
return [__(doc.status), "darkgrey", "status,=," + doc.status];
}
}
if (
flt(doc.outstanding_amount) > 0 &&
doc.docstatus == 1 &&
cint(doc.on_hold)
) {
if (!doc.release_date) {
return [__("On Hold"), "darkgrey"];
} else if (
frappe.datetime.get_diff(
doc.release_date,
frappe.datetime.nowdate()
) > 0
) {
return [__("Temporarily on Hold"), "darkgrey"];
}
}
const status_colors = {
"Unpaid": "orange",
"Paid": "green",
"Return": "gray",
"Overdue": "red",
"Partly Paid": "yellow",
"Internal Transfer": "darkgrey",
};
if (status_colors[doc.status]) {
return [
__(doc.status),
status_colors[doc.status],
"status,=," + doc.status,
];
}
},
};

View File

@@ -97,6 +97,7 @@
"width": "100px"
},
{
"depends_on": "exchange_gain_loss",
"fieldname": "exchange_gain_loss",
"fieldtype": "Currency",
"label": "Exchange Gain/Loss",
@@ -104,6 +105,7 @@
"read_only": 1
},
{
"depends_on": "exchange_gain_loss",
"fieldname": "ref_exchange_rate",
"fieldtype": "Float",
"label": "Reference Exchange Rate",
@@ -115,7 +117,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-04-20 16:26:53.820530",
"modified": "2021-09-26 15:47:28.167371",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Advance",

View File

@@ -446,12 +446,15 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
currency() {
var me = this;
this._super();
$.each(cur_frm.doc.timesheets, function(i, d) {
let row = frappe.get_doc(d.doctype, d.name)
set_timesheet_detail_rate(row.doctype, row.name, cur_frm.doc.currency, row.timesheet_detail)
});
calculate_total_billing_amount(cur_frm)
if (this.frm.doc.timesheets) {
this.frm.doc.timesheets.forEach((d) => {
let row = frappe.get_doc(d.doctype, d.name)
set_timesheet_detail_rate(row.doctype, row.name, me.frm.doc.currency, row.timesheet_detail)
});
frm.trigger("calculate_timesheet_totals");
}
}
});
@@ -999,7 +1002,7 @@ frappe.ui.form.on('Sales Invoice', {
frappe.ui.form.on("Sales Invoice Timesheet", {
timesheets_remove(frm, cdt, cdn) {
timesheets_remove(frm) {
frm.trigger("calculate_timesheet_totals");
}
});

View File

@@ -124,6 +124,13 @@
"total_advance",
"outstanding_amount",
"disable_rounded_total",
"column_break4",
"write_off_amount",
"base_write_off_amount",
"write_off_outstanding_amount_automatically",
"column_break_74",
"write_off_account",
"write_off_cost_center",
"advances_section",
"allocate_advances_automatically",
"get_advances",
@@ -144,13 +151,6 @@
"column_break_90",
"change_amount",
"account_for_change_amount",
"column_break4",
"write_off_amount",
"base_write_off_amount",
"write_off_outstanding_amount_automatically",
"column_break_74",
"write_off_account",
"write_off_cost_center",
"terms_section_break",
"tc_name",
"terms",
@@ -161,14 +161,14 @@
"column_break_84",
"language",
"more_information",
"status",
"inter_company_invoice_reference",
"is_internal_customer",
"represents_company",
"customer_group",
"campaign",
"is_discounted",
"col_break23",
"status",
"is_internal_customer",
"is_discounted",
"source",
"more_info",
"debit_to",
@@ -1652,7 +1652,7 @@
"label": "Status",
"length": 30,
"no_copy": 1,
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nPartly Paid\nUnpaid\nUnpaid and Discounted\nPartly Paid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1,
"read_only": 1
},
@@ -1954,6 +1954,7 @@
"fetch_from": "customer.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Represents Company",
"options": "Company",
"read_only": 1
@@ -1989,16 +1990,6 @@
"label": "Additional Discount Account",
"options": "Account"
},
{
"default": "0",
"fieldname": "ignore_default_payment_terms_template",
"fieldtype": "Check",
"hidden": 1,
"label": "Ignore Default Payment Terms Template",
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "dispatch_address_name",
@@ -2014,6 +2005,14 @@
"label": "Dispatch Address",
"read_only": 1
},
{
"default": "0",
"fieldname": "ignore_default_payment_terms_template",
"fieldtype": "Check",
"hidden": 1,
"label": "Ignore Default Payment Terms Template",
"read_only": 1
},
{
"fieldname": "total_billing_hours",
"fieldtype": "Float",
@@ -2032,11 +2031,12 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2021-09-08 15:24:25.486499",
"modified": "2021-10-11 20:19:38.667508",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -2086,4 +2086,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -230,9 +230,6 @@ class SalesInvoice(SellingController):
# this sequence because outstanding may get -ve
self.make_gl_entries()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
@@ -501,7 +498,7 @@ class SalesInvoice(SellingController):
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details
if not self.pos_profile:
if not self.pos_profile and not self.flags.ignore_pos_profile:
pos_profile = get_pos_profile(self.company) or {}
if not pos_profile:
return
@@ -1299,12 +1296,20 @@ class SalesInvoice(SellingController):
serial_nos = item.serial_no or ""
si_serial_nos = set(get_serial_nos(serial_nos))
serial_no_diff = si_serial_nos - dn_serial_nos
if si_serial_nos - dn_serial_nos:
frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note").format(item.idx))
if serial_no_diff:
dn_link = frappe.utils.get_link_to_form("Delivery Note", item.delivery_note)
serial_no_msg = ", ".join(frappe.bold(d) for d in serial_no_diff)
msg = _("Row #{0}: The following Serial Nos are not present in Delivery Note {1}:").format(
item.idx, dn_link)
msg += " " + serial_no_msg
frappe.throw(msg=msg, title=_("Serial Nos Mismatch"))
if item.serial_no and cint(item.qty) != len(si_serial_nos):
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
frappe.throw(_("Row #{0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
item.idx, item.qty, item.item_code, len(si_serial_nos)))
def update_project(self):
@@ -1472,14 +1477,8 @@ class SalesInvoice(SellingController):
self.status = 'Draft'
return
precision = self.precision("outstanding_amount")
outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
discounting_status = None
if self.is_discounted:
discounting_status = get_discounting_status(self.name)
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
total = get_total_in_party_account_currency(self)
if not status:
if self.docstatus == 2:
@@ -1487,15 +1486,13 @@ class SalesInvoice(SellingController):
elif self.docstatus == 1:
if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discounting_status=='Disbursed':
self.status = "Overdue and Discounted"
elif outstanding_amount > 0 and due_date < nowdate:
elif is_overdue(self, total):
self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discounting_status=='Disbursed':
self.status = "Unpaid and Discounted"
elif outstanding_amount > 0 and due_date >= nowdate:
elif 0 < outstanding_amount < total:
self.status = "Partly Paid"
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
self.status = "Unpaid"
#Check if outstanding amount is 0 due to credit note issued against invoice
# Check if outstanding amount is 0 due to credit note issued against invoice
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
self.status = "Credit Note Issued"
elif self.is_return == 1:
@@ -1504,12 +1501,57 @@ class SalesInvoice(SellingController):
self.status = "Paid"
else:
self.status = "Submitted"
if (
self.status in ("Unpaid", "Partly Paid", "Overdue")
and self.is_discounted
and get_discounting_status(self.name) == "Disbursed"
):
self.status += " and Discounted"
else:
self.status = "Draft"
if update:
self.db_set('status', self.status, update_modified = update_modified)
def get_total_in_party_account_currency(doc):
total_fieldname = (
"grand_total"
if doc.disable_rounded_total
else "rounded_total"
)
if doc.party_account_currency != doc.currency:
total_fieldname = "base_" + total_fieldname
return flt(doc.get(total_fieldname), doc.precision(total_fieldname))
def is_overdue(doc, total):
outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount"))
if outstanding_amount <= 0:
return
today = getdate()
if doc.get('is_pos') or not doc.get('payment_schedule'):
return getdate(doc.due_date) < today
# calculate payable amount till date
payment_amount_field = (
"base_payment_amount"
if doc.party_account_currency != doc.currency
else "payment_amount"
)
payable_amount = sum(
payment.get(payment_amount_field)
for payment in doc.payment_schedule
if getdate(payment.due_date) < today
)
return (total - outstanding_amount) < payable_amount
def get_discounting_status(sales_invoice):
status = None

View File

@@ -6,18 +6,20 @@ frappe.listview_settings['Sales Invoice'] = {
add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return"],
get_indicator: function(doc) {
var status_color = {
const status_colors = {
"Draft": "grey",
"Unpaid": "orange",
"Paid": "green",
"Return": "gray",
"Credit Note Issued": "gray",
"Unpaid and Discounted": "orange",
"Partly Paid and Discounted": "yellow",
"Overdue and Discounted": "red",
"Overdue": "red",
"Partly Paid": "yellow",
"Internal Transfer": "darkgrey"
};
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
return [__(doc.status), status_colors[doc.status], "status,=,"+doc.status];
},
right_column: "grand_total"
};

View File

@@ -131,6 +131,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_payment_entry_unlink_against_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
si = frappe.copy_doc(test_records[0])
si.is_pos = 0
si.insert()
@@ -154,6 +155,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_payment_entry_unlink_against_standalone_credit_note(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
si1 = create_sales_invoice(rate=1000)
si2 = create_sales_invoice(rate=300)
si3 = create_sales_invoice(qty=-1, rate=300, is_return=1)
@@ -1083,8 +1085,6 @@ class TestSalesInvoice(unittest.TestCase):
actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
frappe.db.commit()
self.assertEqual(actual_qty_0 - 5, actual_qty_1)
# outgoing_rate
@@ -1439,15 +1439,22 @@ class TestSalesInvoice(unittest.TestCase):
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
expected_itemised_tax = {
"999800": {
"_Test Item": {
"Service Tax": {
"tax_rate": 10.0,
"tax_amount": 1500.0
"tax_amount": 1000.0
}
},
"_Test Item 2": {
"Service Tax": {
"tax_rate": 10.0,
"tax_amount": 500.0
}
}
}
expected_itemised_taxable_amount = {
"999800": 15000.0
"_Test Item": 10000.0,
"_Test Item 2": 5000.0
}
self.assertEqual(itemised_tax, expected_itemised_tax)
@@ -1658,6 +1665,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_credit_note(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
si = create_sales_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1)
outstanding_amount = get_outstanding_amount(si.doctype,
@@ -1809,6 +1817,47 @@ class TestSalesInvoice(unittest.TestCase):
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
def test_deferred_revenue_post_account_freeze_upto_by_admin(self):
frappe.set_user("Administrator")
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company")
item = create_item("_Test Item for Deferred Accounting")
item.enable_deferred_revenue = 1
item.deferred_revenue_account = deferred_account
item.no_of_months = 12
item.save()
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"
si.items[0].deferred_revenue_account = deferred_account
si.save()
si.submit()
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager')
pda1 = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
posting_date=nowdate(),
start_date="2019-01-01",
end_date="2019-03-31",
type="Income",
company="_Test Company"
))
pda1.insert()
self.assertRaises(frappe.ValidationError, pda1.submit)
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
def test_fixed_deferred_revenue(self):
deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company")
@@ -1908,11 +1957,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
def test_sle_if_target_warehouse_exists_accidentally(self):
"""
Check if inward entry exists if Target Warehouse accidentally exists
but Customer is not an internal customer.
"""
def test_sle_for_target_warehouse(self):
se = make_stock_entry(
item_code="138-CMS Shoe",
target="Finished Goods - _TC",
@@ -1933,9 +1978,9 @@ class TestSalesInvoice(unittest.TestCase):
sles = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": si.name},
fields=["name", "actual_qty"])
# check if only one SLE for outward entry is created
self.assertEqual(len(sles), 1)
self.assertEqual(sles[0].actual_qty, -1)
# check if both SLEs are created
self.assertEqual(len(sles), 2)
self.assertEqual(sum(d.actual_qty for d in sles), 0.0)
# tear down
si.cancel()
@@ -2246,6 +2291,66 @@ class TestSalesInvoice(unittest.TestCase):
party_link.delete()
frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0)
def test_payment_statuses(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
today = nowdate()
# Test Overdue
si = create_sales_invoice(do_not_submit=True)
si.payment_schedule = []
si.append("payment_schedule", {
"due_date": add_days(today, -5),
"invoice_portion": 50,
"payment_amount": si.grand_total / 2
})
si.append("payment_schedule", {
"due_date": add_days(today, 5),
"invoice_portion": 50,
"payment_amount": si.grand_total / 2
})
si.submit()
self.assertEqual(si.status, "Overdue")
# Test payment less than due amount
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_amount = 1
pe.references[0].allocated_amount = pe.paid_amount
pe.submit()
si.reload()
self.assertEqual(si.status, "Overdue")
# Test Partly Paid
pe = frappe.copy_doc(pe)
pe.paid_amount = si.grand_total / 2
pe.references[0].allocated_amount = pe.paid_amount
pe.submit()
si.reload()
self.assertEqual(si.status, "Partly Paid")
# Test Paid
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_amount = si.outstanding_amount
pe.submit()
si.reload()
self.assertEqual(si.status, "Paid")
def test_sales_invoice_submission_post_account_freezing_date(self):
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1))
si = create_sales_invoice(do_not_save=True)
si.posting_date = add_days(getdate(), 1)
si.save()
self.assertRaises(frappe.ValidationError, si.submit)
si.posting_date = getdate()
si.submit()
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'

View File

@@ -98,6 +98,7 @@
"width": "120px"
},
{
"depends_on": "exchange_gain_loss",
"fieldname": "exchange_gain_loss",
"fieldtype": "Currency",
"label": "Exchange Gain/Loss",
@@ -105,6 +106,7 @@
"read_only": 1
},
{
"depends_on": "exchange_gain_loss",
"fieldname": "ref_exchange_rate",
"fieldtype": "Float",
"label": "Reference Exchange Rate",
@@ -116,7 +118,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-06-04 20:25:49.832052",
"modified": "2021-09-26 15:47:46.911595",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Advance",

View File

@@ -16,9 +16,9 @@
"column_break_9",
"billing_amount",
"section_break_11",
"timesheet_detail",
"column_break_5",
"time_sheet",
"timesheet_detail",
"column_break_13",
"project_name"
],
"fields": [
@@ -91,7 +91,6 @@
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"label": "Totals"
@@ -110,11 +109,15 @@
"fieldtype": "Data",
"label": "Project Name",
"read_only": 1
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
}
],
"istable": 1,
"links": [],
"modified": "2021-08-15 18:37:08.084930",
"modified": "2021-10-02 03:48:44.979777",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Timesheet",

View File

@@ -33,7 +33,7 @@ class Subscription(Document):
# update start just before the subscription doc is created
self.update_subscription_period(self.start_date)
def update_subscription_period(self, date=None):
def update_subscription_period(self, date=None, return_date=False):
"""
Subscription period is the period to be billed. This method updates the
beginning of the billing period and end of the billing period.
@@ -41,28 +41,41 @@ class Subscription(Document):
The beginning of the billing period is represented in the doctype as
`current_invoice_start` and the end of the billing period is represented
as `current_invoice_end`.
"""
self.set_current_invoice_start(date)
self.set_current_invoice_end()
def set_current_invoice_start(self, date=None):
If return_date is True, it wont update the start and end dates.
This is implemented to get the dates to check if is_current_invoice_generated
"""
This sets the date of the beginning of the current billing period.
_current_invoice_start = self.get_current_invoice_start(date)
_current_invoice_end = self.get_current_invoice_end(_current_invoice_start)
if return_date:
return _current_invoice_start, _current_invoice_end
self.current_invoice_start = _current_invoice_start
self.current_invoice_end = _current_invoice_end
def get_current_invoice_start(self, date=None):
"""
This returns the date of the beginning of the current billing period.
If the `date` parameter is not given , it will be automatically set as today's
date.
"""
if self.is_new_subscription() and self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date):
self.current_invoice_start = add_days(self.trial_period_end, 1)
elif self.trial_period_start and self.is_trialling():
self.current_invoice_start = self.trial_period_start
elif date:
self.current_invoice_start = date
else:
self.current_invoice_start = nowdate()
_current_invoice_start = None
def set_current_invoice_end(self):
if self.is_new_subscription() and self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date):
_current_invoice_start = add_days(self.trial_period_end, 1)
elif self.trial_period_start and self.is_trialling():
_current_invoice_start = self.trial_period_start
elif date:
_current_invoice_start = date
else:
_current_invoice_start = nowdate()
return _current_invoice_start
def get_current_invoice_end(self, date=None):
"""
This sets the date of the end of the current billing period.
This returns the date of the end of the current billing period.
If the subscription is in trial period, it will be set as the end of the
trial period.
@@ -71,44 +84,47 @@ class Subscription(Document):
current billing period where `x` is the billing interval from the
`Subscription Plan` in the `Subscription`.
"""
if self.is_trialling() and getdate(self.current_invoice_start) < getdate(self.trial_period_end):
self.current_invoice_end = self.trial_period_end
_current_invoice_end = None
if self.is_trialling() and getdate(date) < getdate(self.trial_period_end):
_current_invoice_end = self.trial_period_end
else:
billing_cycle_info = self.get_billing_cycle_data()
if billing_cycle_info:
if self.is_new_subscription() and getdate(self.start_date) < getdate(self.current_invoice_start):
self.current_invoice_end = add_to_date(self.start_date, **billing_cycle_info)
if self.is_new_subscription() and getdate(self.start_date) < getdate(date):
_current_invoice_end = add_to_date(self.start_date, **billing_cycle_info)
# For cases where trial period is for an entire billing interval
if getdate(self.current_invoice_end) < getdate(self.current_invoice_start):
self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info)
if getdate(self.current_invoice_end) < getdate(date):
_current_invoice_end = add_to_date(date, **billing_cycle_info)
else:
self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info)
_current_invoice_end = add_to_date(date, **billing_cycle_info)
else:
self.current_invoice_end = get_last_day(self.current_invoice_start)
_current_invoice_end = get_last_day(date)
if self.follow_calendar_months:
billing_info = self.get_billing_cycle_and_interval()
billing_interval_count = billing_info[0]['billing_interval_count']
calendar_months = get_calendar_months(billing_interval_count)
calendar_month = 0
current_invoice_end_month = getdate(self.current_invoice_end).month
current_invoice_end_year = getdate(self.current_invoice_end).year
current_invoice_end_month = getdate(_current_invoice_end).month
current_invoice_end_year = getdate(_current_invoice_end).year
for month in calendar_months:
if month <= current_invoice_end_month:
calendar_month = month
if cint(calendar_month - billing_interval_count) <= 0 and \
getdate(self.current_invoice_start).month != 1:
getdate(date).month != 1:
calendar_month = 12
current_invoice_end_year -= 1
self.current_invoice_end = get_last_day(cstr(current_invoice_end_year) + '-' \
+ cstr(calendar_month) + '-01')
_current_invoice_end = get_last_day(cstr(current_invoice_end_year) + '-' + cstr(calendar_month) + '-01')
if self.end_date and getdate(self.current_invoice_end) > getdate(self.end_date):
self.current_invoice_end = self.end_date
if self.end_date and getdate(_current_invoice_end) > getdate(self.end_date):
_current_invoice_end = self.end_date
return _current_invoice_end
@staticmethod
def validate_plans_billing_cycle(billing_cycle_data):
@@ -484,8 +500,9 @@ class Subscription(Document):
def is_current_invoice_generated(self):
invoice = self.get_current_invoice()
_current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True)
if invoice and getdate(self.current_invoice_start) <= getdate(invoice.posting_date) <= getdate(self.current_invoice_end):
if invoice and getdate(_current_start_date) <= getdate(invoice.posting_date) <= getdate(_current_end_date):
return True
return False
@@ -538,15 +555,15 @@ class Subscription(Document):
else:
self.set_status_grace_period()
if getdate() > getdate(self.current_invoice_end):
self.update_subscription_period(add_days(self.current_invoice_end, 1))
# Generate invoices periodically even if current invoice are unpaid
if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice()
or self.is_prepaid_to_invoice()):
prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
self.generate_invoice(prorate)
if getdate() > getdate(self.current_invoice_end):
self.update_subscription_period(add_days(self.current_invoice_end, 1))
@staticmethod
def is_paid(invoice):
"""

View File

@@ -18,6 +18,7 @@ from frappe.utils.data import (
from erpnext.accounts.doctype.subscription.subscription import get_prorata_factor
test_dependencies = ("UOM", "Item Group", "Item")
def create_plan():
if not frappe.db.exists('Subscription Plan', '_Test Plan Name'):
@@ -68,7 +69,6 @@ def create_plan():
supplier.insert()
class TestSubscription(unittest.TestCase):
def setUp(self):
create_plan()

View File

@@ -100,6 +100,7 @@ def get_tax_withholding_details(tax_withholding_category, posting_date, company)
for account_detail in tax_withholding.accounts:
if company == account_detail.company:
return frappe._dict({
"tax_withholding_category": tax_withholding_category,
"account_head": account_detail.account,
"rate": tax_rate_detail.tax_withholding_rate,
"from_date": tax_rate_detail.from_date,
@@ -202,22 +203,46 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
# then chargeable value is "prev invoices + advances" value which cross the threshold
tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers)
if cint(tax_details.round_off_tax_amount):
tax_amount = round(tax_amount)
return tax_amount, tax_deducted
def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
doctype = 'Purchase Invoice' if party_type == 'Supplier' else 'Sales Invoice'
filters = {
dr_or_cr: ['>', 0],
'company': company,
'party_type': party_type,
'party': ['in', parties],
frappe.scrub(party_type): ['in', parties],
'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
'is_opening': 'No',
'is_cancelled': 0
'docstatus': 1
}
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""]
if not tax_details.get('consider_party_ledger_amount') and doctype != "Sales Invoice":
filters.update({
'apply_tds': 1,
'tax_withholding_category': tax_details.get('tax_withholding_category')
})
invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""]
journal_entries = frappe.db.sql("""
SELECT j.name
FROM `tabJournal Entry` j, `tabJournal Entry Account` ja
WHERE
j.docstatus = 1
AND j.is_opening = 'No'
AND j.posting_date between %s and %s
AND ja.{dr_or_cr} > 0
AND ja.party in %s
""".format(dr_or_cr=dr_or_cr), (tax_details.from_date, tax_details.to_date, tuple(parties)), as_list=1)
if journal_entries:
journal_entries = journal_entries[0]
return invoices + journal_entries
def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'):
# for advance vouchers, debit and credit is reversed
@@ -300,9 +325,6 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
else:
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
if cint(tax_details.round_off_tax_amount):
tds_amount = round(tds_amount)
return tds_amount
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):

View File

@@ -176,6 +176,29 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in invoices:
d.cancel()
def test_multi_category_single_supplier(self):
frappe.db.set_value("Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category")
invoices = []
pi = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 500, do_not_save=True)
pi.tax_withholding_category = "Test Service Category"
pi.save()
pi.submit()
invoices.append(pi)
# Second Invoice will apply TDS checked
pi1 = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 2500, do_not_save=True)
pi1.tax_withholding_category = "Test Goods Category"
pi1.save()
pi1.submit()
invoices.append(pi1)
self.assertEqual(pi1.taxes[0].tax_amount, 250)
#delete invoices to avoid clashing
for d in invoices:
d.cancel()
def cancel_invoices():
purchase_invoices = frappe.get_all("Purchase Invoice", {
'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']],
@@ -251,7 +274,8 @@ def create_sales_invoice(**args):
def create_records():
# create a new suppliers
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', 'Test TDS Supplier4']:
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3',
'Test TDS Supplier4', 'Test TDS Supplier5']:
if frappe.db.exists('Supplier', name):
continue
@@ -390,3 +414,39 @@ def create_tax_with_holding_category():
'account': 'TDS - _TC'
}]
}).insert()
if not frappe.db.exists("Tax Withholding Category", "Test Service Category"):
frappe.get_doc({
"doctype": "Tax Withholding Category",
"name": "Test Service Category",
"category_name": "Test Service Category",
"rates": [{
'from_date': fiscal_year[1],
'to_date': fiscal_year[2],
'tax_withholding_rate': 10,
'single_threshold': 2000,
'cumulative_threshold': 2000
}],
"accounts": [{
'company': '_Test Company',
'account': 'TDS - _TC'
}]
}).insert()
if not frappe.db.exists("Tax Withholding Category", "Test Goods Category"):
frappe.get_doc({
"doctype": "Tax Withholding Category",
"name": "Test Goods Category",
"category_name": "Test Goods Category",
"rates": [{
'from_date': fiscal_year[1],
'to_date': fiscal_year[2],
'tax_withholding_rate': 10,
'single_threshold': 2000,
'cumulative_threshold': 2000
}],
"accounts": [{
'company': '_Test Company',
'account': 'TDS - _TC'
}]
}).insert()

View File

@@ -284,13 +284,16 @@ def check_freezing_date(posting_date, adv_adj=False):
"""
Nobody can do GL Entries where posting date is before freezing date
except authorized person
Administrator has all the roles so this check will be bypassed if any role is allowed to post
Hence stop admin to bypass if accounts are freezed
"""
if not adv_adj:
acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
if acc_frozen_upto:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if getdate(posting_date) <= getdate(acc_frozen_upto) \
and not frozen_accounts_modifier in frappe.get_roles():
and (frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == 'Administrator'):
frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
def set_as_cancel(voucher_type, voucher_no):

View File

@@ -139,9 +139,9 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
data["total"] = total
return data
def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters={}):
def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters=None):
cond = ""
filters = frappe._dict(filters)
filters = frappe._dict(filters or {})
if filters.include_default_book_entries:
company_fb = frappe.db.get_value("Company", company, 'default_finance_book')

View File

@@ -103,8 +103,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
column.is_tree = true;
}
value = default_formatter(value, row, column, data);
if (data && data.account && column.apply_currency_formatter) {
data.currency = erpnext.get_currency(column.company_name);
}
value = default_formatter(value, row, column, data);
if (!data.parent_account) {
value = $(`<span>${value}</span>`);

View File

@@ -3,12 +3,14 @@
from __future__ import unicode_literals
from collections import defaultdict
import frappe
from frappe import _
from frappe.utils import cint, flt, getdate
import erpnext
from erpnext.accounts.report.balance_sheet.balance_sheet import (
check_opening_balance,
get_chart_data,
get_provisional_profit_loss,
)
@@ -31,7 +33,7 @@ from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
get_report_summary as get_pl_summary,
)
from erpnext.accounts.report.utils import convert_to_presentation_currency
from erpnext.accounts.report.utils import convert, convert_to_presentation_currency
def execute(filters=None):
@@ -42,7 +44,7 @@ def execute(filters=None):
fiscal_year = get_fiscal_year_data(filters.get('from_fiscal_year'), filters.get('to_fiscal_year'))
companies_column, companies = get_companies(filters)
columns = get_columns(companies_column)
columns = get_columns(companies_column, filters)
if filters.get('report') == "Balance Sheet":
data, message, chart, report_summary = get_balance_sheet_data(fiscal_year, companies, columns, filters)
@@ -73,21 +75,24 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity,
companies, filters.get('company'), company_currency, True)
message, opening_balance = check_opening_balance(asset, liability, equity)
message, opening_balance = prepare_companywise_opening_balance(asset, liability, equity, companies)
if opening_balance and round(opening_balance,2) !=0:
unclosed ={
if opening_balance:
unclosed = {
"account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
"account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
"warn_if_negative": True,
"currency": company_currency
}
for company in companies:
unclosed[company] = opening_balance
if provisional_profit_loss:
provisional_profit_loss[company] = provisional_profit_loss[company] - opening_balance
unclosed["total"]=opening_balance
for company in companies:
unclosed[company] = opening_balance.get(company)
if provisional_profit_loss and provisional_profit_loss.get(company):
provisional_profit_loss[company] = (
flt(provisional_profit_loss[company]) - flt(opening_balance.get(company))
)
unclosed["total"] = opening_balance.get(company)
data.append(unclosed)
if provisional_profit_loss:
@@ -102,6 +107,37 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
return data, message, chart, report_summary
def prepare_companywise_opening_balance(asset_data, liability_data, equity_data, companies):
opening_balance = {}
for company in companies:
opening_value = 0
# opening_value = Aseet - liability - equity
for data in [asset_data, liability_data, equity_data]:
account_name = get_root_account_name(data[0].root_type, company)
opening_value += get_opening_balance(account_name, data, company)
opening_balance[company] = opening_value
if opening_balance:
return _("Previous Financial Year is not closed"), opening_balance
return '', {}
def get_opening_balance(account_name, data, company):
for row in data:
if row.get('account_name') == account_name:
return row.get('company_wise_opening_bal', {}).get(company, 0.0)
def get_root_account_name(root_type, company):
return frappe.get_all(
'Account',
fields=['account_name'],
filters = {'root_type': root_type, 'is_group': 1,
'company': company, 'parent_account': ('is', 'not set')},
as_list=1
)[0][0]
def get_profit_loss_data(fiscal_year, companies, columns, filters):
income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters)
company_currency = get_company_currency(filters)
@@ -193,30 +229,37 @@ def get_account_type_based_data(account_type, companies, fiscal_year, filters):
data["total"] = total
return data
def get_columns(companies):
columns = [{
"fieldname": "account",
"label": _("Account"),
"fieldtype": "Link",
"options": "Account",
"width": 300
}]
columns.append({
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"hidden": 1
})
def get_columns(companies, filters):
columns = [
{
"fieldname": "account",
"label": _("Account"),
"fieldtype": "Link",
"options": "Account",
"width": 300
}, {
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"hidden": 1
}
]
for company in companies:
apply_currency_formatter = 1 if not filters.presentation_currency else 0
currency = filters.presentation_currency
if not currency:
currency = erpnext.get_company_currency(company)
columns.append({
"fieldname": company,
"label": company,
"label": f'{company} ({currency})',
"fieldtype": "Currency",
"options": "currency",
"width": 150
"width": 150,
"apply_currency_formatter": apply_currency_formatter,
"company_name": company
})
return columns
@@ -236,6 +279,8 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None
end_date = filters.period_end_date
filters.end_date = end_date
gl_entries_by_account = {}
for root in frappe.db.sql("""select lft, rgt from tabAccount
where root_type=%s and ifnull(parent_account, '') = ''""", root_type, as_dict=1):
@@ -244,9 +289,10 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
end_date, root.lft, root.rgt, filters,
gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False)
calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters)
calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year)
accumulate_values_into_parents(accounts, accounts_by_name, companies)
out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency)
out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters)
if out:
add_total_row(out, root_type, balance_must_be, companies, company_currency)
@@ -257,7 +303,10 @@ def get_company_currency(filters=None):
return (filters.get('presentation_currency')
or frappe.get_cached_value('Company', filters.company, "default_currency"))
def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters):
def calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year):
start_date = (fiscal_year.year_start_date
if filters.filter_based_on == 'Fiscal Year' else filters.period_start_date)
for entries in gl_entries_by_account.values():
for entry in entries:
if entry.account_number:
@@ -266,15 +315,32 @@ def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_d
account_name = entry.account_name
d = accounts_by_name.get(account_name)
if d:
debit, credit = 0, 0
for company in companies:
# check if posting date is within the period
if (entry.company == company or (filters.get('accumulated_in_group_company'))
and entry.company in companies.get(company)):
d[company] = d.get(company, 0.0) + flt(entry.debit) - flt(entry.credit)
parent_company_currency = erpnext.get_company_currency(d.company)
child_company_currency = erpnext.get_company_currency(entry.company)
debit, credit = flt(entry.debit), flt(entry.credit)
if (not filters.get('presentation_currency')
and entry.company != company
and parent_company_currency != child_company_currency
and filters.get('accumulated_in_group_company')):
debit = convert(debit, parent_company_currency, child_company_currency, filters.end_date)
credit = convert(credit, parent_company_currency, child_company_currency, filters.end_date)
d[company] = d.get(company, 0.0) + flt(debit) - flt(credit)
if entry.posting_date < getdate(start_date):
d['company_wise_opening_bal'][company] += (flt(debit) - flt(credit))
if entry.posting_date < getdate(start_date):
d["opening_balance"] = d.get("opening_balance", 0.0) + flt(entry.debit) - flt(entry.credit)
d["opening_balance"] = d.get("opening_balance", 0.0) + flt(debit) - flt(credit)
def accumulate_values_into_parents(accounts, accounts_by_name, companies):
"""accumulate children's values in parent accounts"""
@@ -282,17 +348,18 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
if d.parent_account:
account = d.parent_account_name
if not accounts_by_name.get(account):
continue
# if not accounts_by_name.get(account):
# continue
for company in companies:
accounts_by_name[account][company] = \
accounts_by_name[account].get(company, 0.0) + d.get(company, 0.0)
accounts_by_name[account]['company_wise_opening_bal'][company] += d.get('company_wise_opening_bal', {}).get(company, 0.0)
accounts_by_name[account]["opening_balance"] = \
accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
def get_account_heads(root_type, companies, filters):
accounts = get_accounts(root_type, filters)
@@ -353,7 +420,7 @@ def get_accounts(root_type, filters):
`tabAccount` where company = %s and root_type = %s
""" , (filters.get('company'), root_type), as_dict=1)
def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency):
def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters):
data = []
for d in accounts:
@@ -367,10 +434,13 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
"parent_account": _(d.parent_account),
"indent": flt(d.indent),
"year_start_date": start_date,
"root_type": d.root_type,
"year_end_date": end_date,
"currency": company_currency,
"currency": filters.presentation_currency,
"company_wise_opening_bal": d.company_wise_opening_bal,
"opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1)
})
for company in companies:
if d.get(company) and balance_must_be == "Credit":
# change sign based on Debit or Credit, since calculation is done using (debit - credit)
@@ -385,6 +455,7 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
row["has_value"] = has_value
row["total"] = total
data.append(row)
return data
@@ -447,6 +518,7 @@ def get_account_details(account):
'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1)
def validate_entries(key, entry, accounts_by_name, accounts):
# If an account present in the child company and not in the parent company
if key not in accounts_by_name:
args = get_account_details(entry.account)
@@ -456,12 +528,23 @@ def validate_entries(key, entry, accounts_by_name, accounts):
args.update({
'lft': parent_args.lft + 1,
'rgt': parent_args.rgt - 1,
'indent': 3,
'root_type': parent_args.root_type,
'report_type': parent_args.report_type
'report_type': parent_args.report_type,
'parent_account_name': parent_args.account_name,
'company_wise_opening_bal': defaultdict(float)
})
accounts_by_name.setdefault(key, args)
accounts.append(args)
idx = len(accounts)
# To identify parent account index
for index, row in enumerate(accounts):
if row.parent_account_name == args.parent_account_name:
idx = index
break
accounts.insert(idx+1, args)
def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions = []
@@ -491,7 +574,6 @@ def add_total_row(out, root_type, balance_must_be, companies, company_currency):
for company in companies:
total_row.setdefault(company, 0.0)
total_row[company] += row.get(company, 0.0)
row[company] = 0.0
total_row.setdefault("total", 0.0)
total_row["total"] += flt(row["total"])
@@ -511,6 +593,7 @@ def filter_accounts(accounts, depth=10):
account_name = d.account_number + ' - ' + d.account_name
else:
account_name = d.account_name
d['company_wise_opening_bal'] = defaultdict(float)
accounts_by_name[account_name] = d
parent_children_map.setdefault(d.parent_account or None, []).append(d)

View File

@@ -421,8 +421,6 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
update_value_in_dict(totals, 'closing', gle)
elif gle.posting_date <= to_date:
update_value_in_dict(gle_map[gle.get(group_by)].totals, 'total', gle)
update_value_in_dict(totals, 'total', gle)
if filters.get("group_by") != 'Group by Voucher (Consolidated)':
gle_map[gle.get(group_by)].entries.append(gle)
elif filters.get("group_by") == 'Group by Voucher (Consolidated)':
@@ -436,10 +434,11 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
else:
update_value_in_dict(consolidated_gle, key, gle)
update_value_in_dict(gle_map[gle.get(group_by)].totals, 'closing', gle)
update_value_in_dict(totals, 'closing', gle)
for key, value in consolidated_gle.items():
update_value_in_dict(gle_map[value.get(group_by)].totals, 'total', value)
update_value_in_dict(totals, 'total', value)
update_value_in_dict(gle_map[value.get(group_by)].totals, 'closing', value)
update_value_in_dict(totals, 'closing', value)
entries.append(value)
return totals, entries

View File

@@ -1,12 +1,15 @@
{
"add_total_row": 0,
"add_total_row": 1,
"columns": [],
"creation": "2018-08-21 11:25:00.551823",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2018-09-21 11:25:00.551823",
"modified": "2021-09-20 17:43:39.518851",
"modified_by": "Administrator",
"module": "Accounts",
"name": "TDS Computation Summary",

View File

@@ -2,11 +2,10 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_advance_vouchers,
get_debit_note_amount,
from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import (
get_result,
get_tds_docs,
)
from erpnext.accounts.utils import get_fiscal_year
@@ -17,9 +16,12 @@ def execute(filters=None):
filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
columns = get_columns(filters)
res = get_result(filters)
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
return columns, res
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
final_result = group_by_supplier_and_category(res)
return columns, final_result
def validate_filters(filters):
''' Validate if dates are properly set and lie in the same fiscal year'''
@@ -33,81 +35,39 @@ def validate_filters(filters):
filters["fiscal_year"] = from_year
def get_result(filters):
# if no supplier selected, fetch data for all tds applicable supplier
# else fetch relevant data for selected supplier
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
fields = ["name", pan+" as pan", "tax_withholding_category", "supplier_type", "supplier_name"]
def group_by_supplier_and_category(data):
supplier_category_wise_map = {}
if filters.supplier:
filters.supplier = frappe.db.get_list('Supplier',
{"name": filters.supplier}, fields)
else:
filters.supplier = frappe.db.get_list('Supplier',
{"tax_withholding_category": ["!=", ""]}, fields)
for row in data:
supplier_category_wise_map.setdefault((row.get('supplier'), row.get('section_code')), {
'pan': row.get('pan'),
'supplier': row.get('supplier'),
'supplier_name': row.get('supplier_name'),
'section_code': row.get('section_code'),
'entity_type': row.get('entity_type'),
'tds_rate': row.get('tds_rate'),
'total_amount_credited': 0.0,
'tds_deducted': 0.0
})
supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['total_amount_credited'] += \
row.get('total_amount_credited', 0.0)
supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['tds_deducted'] += \
row.get('tds_deducted', 0.0)
final_result = get_final_result(supplier_category_wise_map)
return final_result
def get_final_result(supplier_category_wise_map):
out = []
for supplier in filters.supplier:
tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category)
rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year]
if rate:
rate = rate[0]
try:
account = [d.account for d in tds.accounts if d.company == filters.company][0]
except IndexError:
account = []
total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account,
filters.company, filters.from_date, filters.to_date, filters.fiscal_year)
if total_invoiced_amount or tds_deducted:
row = [supplier.pan, supplier.name]
if filters.naming_series == 'Naming Series':
row.append(supplier.supplier_name)
row.extend([tds.name, supplier.supplier_type, rate, total_invoiced_amount, tds_deducted])
out.append(row)
for key, value in supplier_category_wise_map.items():
out.append(value)
return out
def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, fiscal_year):
''' calculate total invoice amount and total tds deducted for given supplier '''
entries = frappe.db.sql("""
select voucher_no, credit
from `tabGL Entry`
where party in (%s) and credit > 0
and company=%s and is_cancelled = 0
and posting_date between %s and %s
""", (supplier, company, from_date, to_date), as_dict=1)
supplier_credit_amount = flt(sum(d.credit for d in entries))
vouchers = [d.voucher_no for d in entries]
vouchers += get_advance_vouchers([supplier], company=company,
from_date=from_date, to_date=to_date)
tds_deducted = 0
if vouchers:
tds_deducted = flt(frappe.db.sql("""
select sum(credit)
from `tabGL Entry`
where account=%s and posting_date between %s and %s
and company=%s and credit > 0 and voucher_no in ({0})
""".format(', '.join("'%s'" % d for d in vouchers)),
(account, from_date, to_date, company))[0][0])
date_range_filter = [fiscal_year, from_date, to_date]
debit_note_amount = get_debit_note_amount([supplier], date_range_filter, company=company)
total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount
return total_invoiced_amount, tds_deducted
def get_columns(filters):
columns = [
{
@@ -149,7 +109,7 @@ def get_columns(filters):
{
"label": _("TDS Rate %"),
"fieldname": "tds_rate",
"fieldtype": "Float",
"fieldtype": "Percent",
"width": 90
},
{

View File

@@ -16,69 +16,6 @@ frappe.query_reports["TDS Payable Monthly"] = {
"label": __("Supplier"),
"fieldtype": "Link",
"options": "Supplier",
"get_query": function() {
return {
"filters": {
"tax_withholding_category": ["!=", ""],
}
}
},
on_change: function() {
frappe.query_report.set_filter_value("purchase_invoice", "");
frappe.query_report.refresh();
}
},
{
"fieldname":"purchase_invoice",
"label": __("Purchase Invoice"),
"fieldtype": "Link",
"options": "Purchase Invoice",
"get_query": function() {
return {
"filters": {
"name": ["in", frappe.query_report.invoices]
}
}
},
on_change: function() {
let supplier = frappe.query_report.get_filter_value('supplier');
if(!supplier) return; // return if no supplier selected
// filter invoices based on selected supplier
let invoices = [];
frappe.query_report.invoice_data.map(d => {
if(d.supplier==supplier)
invoices.push(d.name)
});
frappe.query_report.invoices = invoices;
frappe.query_report.refresh();
}
},
{
"fieldname":"purchase_order",
"label": __("Purchase Order"),
"fieldtype": "Link",
"options": "Purchase Order",
"get_query": function() {
return {
"filters": {
"name": ["in", frappe.query_report.invoices]
}
}
},
on_change: function() {
let supplier = frappe.query_report.get_filter_value('supplier');
if(!supplier) return; // return if no supplier selected
// filter invoices based on selected supplier
let invoices = [];
frappe.query_report.invoice_data.map(d => {
if(d.supplier==supplier)
invoices.push(d.name)
});
frappe.query_report.invoices = invoices;
frappe.query_report.refresh();
}
},
{
"fieldname":"from_date",
@@ -96,23 +33,5 @@ frappe.query_reports["TDS Payable Monthly"] = {
"reqd": 1,
"width": "60px"
}
],
onload: function(report) {
// fetch all tds applied invoices
frappe.call({
"method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders",
callback: function(r) {
let invoices = [];
r.message.map(d => {
invoices.push(d.name);
});
report["invoice_data"] = r.message.invoices;
report["invoices"] = invoices;
}
});
}
]
}

View File

@@ -1,13 +1,15 @@
{
"add_total_row": 1,
"columns": [],
"creation": "2018-08-21 11:32:30.874923",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2019-09-24 13:46:16.473711",
"modified": "2021-09-20 12:05:50.387572",
"modified_by": "Administrator",
"module": "Accounts",
"name": "TDS Payable Monthly",

View File

@@ -8,19 +8,12 @@ from frappe import _
def execute(filters=None):
filters["invoices"] = frappe.cache().hget("invoices", frappe.session.user)
validate_filters(filters)
set_filters(filters)
# TDS payment entries
payment_entries = get_payment_entires(filters)
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
columns = get_columns(filters)
if not filters.get("invoices"):
return columns, []
res = get_result(filters, payment_entries)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
return columns, res
def validate_filters(filters):
@@ -28,109 +21,59 @@ def validate_filters(filters):
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
def set_filters(filters):
invoices = []
if not filters.get("invoices"):
filters["invoices"] = get_tds_invoices_and_orders()
if filters.supplier and filters.purchase_invoice:
for d in filters["invoices"]:
if d.name == filters.purchase_invoice and d.supplier == filters.supplier:
invoices.append(d)
elif filters.supplier and not filters.purchase_invoice:
for d in filters["invoices"]:
if d.supplier == filters.supplier:
invoices.append(d)
elif filters.purchase_invoice and not filters.supplier:
for d in filters["invoices"]:
if d.name == filters.purchase_invoice:
invoices.append(d)
elif filters.supplier and filters.purchase_order:
for d in filters.get("invoices"):
if d.name == filters.purchase_order and d.supplier == filters.supplier:
invoices.append(d)
elif filters.supplier and not filters.purchase_order:
for d in filters.get("invoices"):
if d.supplier == filters.supplier:
invoices.append(d)
elif filters.purchase_order and not filters.supplier:
for d in filters.get("invoices"):
if d.name == filters.purchase_order:
invoices.append(d)
filters["invoices"] = invoices if invoices else filters["invoices"]
filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
#print(filters.get('invoices'))
def get_result(filters, payment_entries):
supplier_map, tds_docs = get_supplier_map(filters, payment_entries)
documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries]
gle_map = get_gle_map(filters, documents)
def get_result(filters, tds_docs, tds_accounts, tax_category_map):
supplier_map = get_supplier_pan_map()
tax_rate_map = get_tax_rate_map(filters)
gle_map = get_gle_map(filters, tds_docs)
out = []
for d in gle_map:
for name, details in gle_map.items():
tds_deducted, total_amount_credited = 0, 0
supplier = supplier_map[d]
tax_withholding_category = tax_category_map.get(name)
rate = tax_rate_map.get(tax_withholding_category)
tds_doc = tds_docs[supplier.tax_withholding_category]
account_list = [i.account for i in tds_doc.accounts if i.company == filters.company]
for entry in details:
supplier = entry.party or entry.against
posting_date = entry.posting_date
voucher_type = entry.voucher_type
if account_list:
account = account_list[0]
if entry.account in tds_accounts:
tds_deducted += (entry.credit - entry.debit)
for k in gle_map[d]:
if k.party == supplier_map[d] and k.credit > 0:
total_amount_credited += (k.credit - k.debit)
elif account_list and k.account == account and (k.credit - k.debit) > 0:
tds_deducted = (k.credit - k.debit)
total_amount_credited += (k.credit - k.debit)
voucher_type = k.voucher_type
total_amount_credited += (entry.credit - entry.debit)
rate = [i.tax_withholding_rate for i in tds_doc.rates
if i.fiscal_year == gle_map[d][0].fiscal_year]
if rate and len(rate) > 0 and tds_deducted:
rate = rate[0]
row = [supplier.pan, supplier.name]
if rate and tds_deducted:
row = {
'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier).pan,
'supplier': supplier_map.get(supplier).name
}
if filters.naming_series == 'Naming Series':
row.append(supplier.supplier_name)
row.update({'supplier_name': supplier_map.get(supplier).supplier_name})
row.update({
'section_code': tax_withholding_category,
'entity_type': supplier_map.get(supplier).supplier_type,
'tds_rate': rate,
'total_amount_credited': total_amount_credited,
'tds_deducted': tds_deducted,
'transaction_date': posting_date,
'transaction_type': voucher_type,
'ref_no': name
})
row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited,
tds_deducted, gle_map[d][0].posting_date, voucher_type, d])
out.append(row)
return out
def get_supplier_map(filters, payment_entries):
# create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}}
# pre-fetch all distinct applicable tds docs
supplier_map, tds_docs = {}, {}
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
supplier_list = [d.supplier for d in filters["invoices"]]
def get_supplier_pan_map():
supplier_map = frappe._dict()
suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name'])
supplier_detail = frappe.db.get_all('Supplier',
{"name": ["in", supplier_list]},
["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"])
for d in suppliers:
supplier_map[d.name] = d
for d in filters["invoices"]:
supplier_map[d.get("name")] = [k for k in supplier_detail
if k.name == d.get("supplier")][0]
for d in payment_entries:
supplier_map[d.get("name")] = [k for k in supplier_detail
if k.name == d.get("supplier")][0]
for d in supplier_detail:
if d.get("tax_withholding_category") not in tds_docs:
tds_docs[d.get("tax_withholding_category")] = \
frappe.get_doc("Tax Withholding Category", d.get("tax_withholding_category"))
return supplier_map, tds_docs
return supplier_map
def get_gle_map(filters, documents):
# create gle_map of the form
@@ -140,10 +83,9 @@ def get_gle_map(filters, documents):
gle = frappe.db.get_all('GL Entry',
{
"voucher_no": ["in", documents],
'is_cancelled': 0,
'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
"credit": (">", 0)
},
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date", "voucher_type"],
["credit", "debit", "account", "voucher_no", "posting_date", "voucher_type", "against", "party"],
)
for d in gle:
@@ -233,39 +175,57 @@ def get_columns(filters):
return columns
def get_payment_entires(filters):
filter_dict = {
'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
'party_type': 'Supplier',
'apply_tax_withholding_amount': 1
def get_tds_docs(filters):
tds_documents = []
purchase_invoices = []
payment_entries = []
journal_entries = []
tax_category_map = {}
tds_accounts = frappe.get_all("Tax Withholding Account", {'company': filters.get('company')},
pluck="account")
query_filters = {
"credit": ('>', 0),
"account": ("in", tds_accounts),
"posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
"is_cancelled": 0
}
if filters.get('purchase_invoice') or filters.get('purchase_order'):
parent = frappe.db.get_all('Payment Entry Reference',
{'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent'])
if filters.get('supplier'):
query_filters.update({'against': filters.get('supplier')})
filter_dict.update({'name': ('in', [d.get('parent') for d in parent])})
tds_docs = frappe.get_all("GL Entry", query_filters, ["voucher_no", "voucher_type", "against", "party"])
payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'],
filters=filter_dict)
for d in tds_docs:
if d.voucher_type == "Purchase Invoice":
purchase_invoices.append(d.voucher_no)
elif d.voucher_type == "Payment Entry":
payment_entries.append(d.voucher_no)
elif d.voucher_type == "Journal Entry":
journal_entries.append(d.voucher_no)
return payment_entries
tds_documents.append(d.voucher_no)
@frappe.whitelist()
def get_tds_invoices_and_orders():
# fetch tds applicable supplier and fetch invoices for these suppliers
suppliers = [d.name for d in frappe.db.get_list("Supplier",
{"tax_withholding_category": ["!=", ""]}, ["name"])]
if purchase_invoices:
get_tax_category_map(purchase_invoices, 'Purchase Invoice', tax_category_map)
invoices = frappe.db.get_list("Purchase Invoice",
{"supplier": ["in", suppliers]}, ["name", "supplier"])
if payment_entries:
get_tax_category_map(payment_entries, 'Payment Entry', tax_category_map)
orders = frappe.db.get_list("Purchase Order",
{"supplier": ["in", suppliers]}, ["name", "supplier"])
if journal_entries:
get_tax_category_map(journal_entries, 'Journal Entry', tax_category_map)
invoices = invoices + orders
invoices = [d for d in invoices if d.supplier]
return tds_documents, tds_accounts, tax_category_map
frappe.cache().hset("invoices", frappe.session.user, invoices)
def get_tax_category_map(vouchers, doctype, tax_category_map):
tax_category_map.update(frappe._dict(frappe.get_all(doctype,
filters = {'name': ('in', vouchers)}, fields=['name', 'tax_withholding_category'], as_list=1)))
return invoices
def get_tax_rate_map(filters):
rate_map = frappe.get_all('Tax Withholding Rate', filters={
'from_date': ('<=', filters.get('from_date')),
'to_date': ('>=', filters.get('to_date'))
}, fields=['parent', 'tax_withholding_rate'], as_list=1)
return frappe._dict(rate_map)

View File

@@ -4,9 +4,10 @@
frappe.query_reports["Unpaid Expense Claim"] = {
"filters": [
{
"fieldname":"employee",
"fieldname": "employee",
"label": __("Employee"),
"fieldtype": "Link"
"fieldtype": "Link",
"options": "Employee"
}
]
}

View File

@@ -4,11 +4,14 @@
from __future__ import unicode_literals
from json import loads
import frappe
import frappe.defaults
from frappe import _, throw
from frappe.model.meta import get_field_precision
from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate
from six import string_types
import erpnext
@@ -787,16 +790,28 @@ def get_children(doctype, parent, company, is_root=False):
if doctype == 'Account':
sort_accounts(acc, is_root, key="value")
company_currency = frappe.get_cached_value('Company', company, "default_currency")
for each in acc:
each["company_currency"] = company_currency
each["balance"] = flt(get_balance_on(each.get("value"), in_account_currency=False, company=company))
if each.account_currency != company_currency:
each["balance_in_account_currency"] = flt(get_balance_on(each.get("value"), company=company))
return acc
@frappe.whitelist()
def get_account_balances(accounts, company):
if isinstance(accounts, string_types):
accounts = loads(accounts)
if not accounts:
return []
company_currency = frappe.get_cached_value("Company", company, "default_currency")
for account in accounts:
account["company_currency"] = company_currency
account["balance"] = flt(get_balance_on(account["value"], in_account_currency=False, company=company))
if account["account_currency"] and account["account_currency"] != company_currency:
account["balance_in_account_currency"] = flt(get_balance_on(account["value"], company=company))
return accounts
def create_payment_gateway_account(gateway, payment_channel="Email"):
from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account

View File

@@ -140,11 +140,6 @@ class Asset(AccountsController):
if self.is_existing_asset:
return
docname = self.purchase_receipt or self.purchase_invoice
if docname:
doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
date = frappe.db.get_value(doctype, docname, 'posting_date')
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
frappe.throw(_("Available-for-use Date should be after purchase date"))
@@ -199,7 +194,7 @@ class Asset(AccountsController):
start = self.clear_depreciation_schedule()
# value_after_depreciation - current Asset value
if d.value_after_depreciation:
if self.docstatus == 1 and d.value_after_depreciation:
value_after_depreciation = (flt(d.value_after_depreciation) -
flt(self.opening_accumulated_depreciation))
else:
@@ -394,10 +389,6 @@ class Asset(AccountsController):
if cint(self.number_of_depreciations_booked) > cint(row.total_number_of_depreciations):
frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations"))
if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(nowdate()):
frappe.msgprint(_("Depreciation Row {0}: Depreciation Start Date is entered as past date")
.format(row.idx), title=_('Warning'), indicator='red')
if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date):
frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date")
.format(row.idx))
@@ -444,9 +435,10 @@ class Asset(AccountsController):
if accumulated_depreciation_after_full_schedule:
accumulated_depreciation_after_full_schedule = max(accumulated_depreciation_after_full_schedule)
asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) -
flt(accumulated_depreciation_after_full_schedule),
self.precision('gross_purchase_amount'))
asset_value_after_full_schedule = flt(
flt(self.gross_purchase_amount) -
flt(self.opening_accumulated_depreciation) -
flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount'))
if (row.expected_value_after_useful_life and
row.expected_value_after_useful_life < asset_value_after_full_schedule):

View File

@@ -645,12 +645,18 @@ class TestAsset(unittest.TestCase):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=8000.0, location="Test Location")
finance_book = frappe.new_doc('Finance Book')
finance_book.finance_book_name = 'Income Tax'
finance_book.for_income_tax = 1
finance_book.insert(ignore_if_duplicate=1)
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.available_for_use_date = '2030-07-12'
asset.purchase_date = '2030-01-01'
asset.append("finance_books", {
"finance_book": finance_book.name,
"expected_value_after_useful_life": 1000,
"depreciation_method": "Written Down Value",
"total_number_of_depreciations": 3,
@@ -676,6 +682,27 @@ class TestAsset(unittest.TestCase):
# reset indian company
frappe.flags.company = company_flag
def test_expected_value_change(self):
"""
tests if changing `expected_value_after_useful_life`
affects `value_after_depreciation`
"""
asset = create_asset(calculate_depreciation=1)
asset.opening_accumulated_depreciation = 2000
asset.number_of_depreciations_booked = 1
asset.finance_books[0].expected_value_after_useful_life = 100
asset.save()
asset.reload()
self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0)
# changing expected_value_after_useful_life shouldn't affect value_after_depreciation
asset.finance_books[0].expected_value_after_useful_life = 200
asset.save()
asset.reload()
self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0)
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category()

View File

@@ -22,7 +22,7 @@ class TestAssetRepair(unittest.TestCase):
frappe.db.sql("delete from `tabTax Rule`")
def test_update_status(self):
asset = create_asset()
asset = create_asset(submit=1)
initial_status = asset.status
asset_repair = create_asset_repair(asset = asset)
@@ -76,7 +76,7 @@ class TestAssetRepair(unittest.TestCase):
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
def test_increase_in_asset_value_due_to_stock_consumption(self):
asset = create_asset(calculate_depreciation = 1)
asset = create_asset(calculate_depreciation = 1, submit=1)
initial_asset_value = get_asset_value(asset)
asset_repair = create_asset_repair(asset= asset, stock_consumption = 1, submit = 1)
asset.reload()
@@ -85,7 +85,7 @@ class TestAssetRepair(unittest.TestCase):
self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value)
def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
asset = create_asset(calculate_depreciation = 1)
asset = create_asset(calculate_depreciation = 1, submit=1)
initial_asset_value = get_asset_value(asset)
asset_repair = create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
asset.reload()
@@ -103,7 +103,7 @@ class TestAssetRepair(unittest.TestCase):
self.assertEqual(asset_repair.name, gl_entry.voucher_no)
def test_increase_in_asset_life(self):
asset = create_asset(calculate_depreciation = 1)
asset = create_asset(calculate_depreciation = 1, submit=1)
initial_num_of_depreciations = num_of_depreciations(asset)
create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
asset.reload()
@@ -126,7 +126,7 @@ def create_asset_repair(**args):
if args.asset:
asset = args.asset
else:
asset = create_asset(is_existing_asset = 1)
asset = create_asset(is_existing_asset = 1, submit=1)
asset_repair = frappe.new_doc("Asset Repair")
asset_repair.update({
"asset": asset.name,

View File

@@ -11,7 +11,7 @@ frappe.tour['Buying Settings'] = [
{
fieldname: "supp_master_name",
title: "Supplier Naming By",
description: __("By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a ") + "<a href='https://docs.erpnext.com/docs/user/manual/en/setting-up/settings/naming-series' target='_blank'>Naming Series</a>" + __(" choose the 'Naming Series' option."),
description: __("By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a <a href='https://docs.erpnext.com/docs/user/manual/en/setting-up/settings/naming-series' target='_blank'>Naming Series</a> choose the 'Naming Series' option."),
},
{
fieldname: "buying_price_list",

View File

@@ -28,7 +28,7 @@
"fieldname": "supp_master_name",
"fieldtype": "Select",
"label": "Supplier Naming By",
"options": "Supplier Name\nNaming Series"
"options": "Supplier Name\nNaming Series\nAuto Name"
},
{
"fieldname": "supplier_group",
@@ -123,7 +123,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-06-24 10:38:28.934525",
"modified": "2021-09-08 19:26:23.548837",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

View File

@@ -1121,6 +1121,7 @@
"fetch_from": "supplier.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Represents Company",
"options": "Company",
"read_only": 1
@@ -1143,7 +1144,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2021-08-30 20:03:14.008804",
"modified": "2021-09-28 13:10:47.955401",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -394,12 +394,10 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc =
@frappe.whitelist()
def get_supplier_tag():
if not frappe.cache().hget("Supplier", "Tags"):
filters = {"document_type": "Supplier"}
tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag))
frappe.cache().hset("Supplier", "Tags", tags)
filters = {"document_type": "Supplier"}
tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag))
return frappe.cache().hget("Supplier", "Tags")
return tags
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs

View File

@@ -433,12 +433,12 @@
"image_field": "image",
"links": [
{
"group": "Item Group",
"link_doctype": "Supplier Item Group",
"link_fieldname": "supplier"
"group": "Allowed Items",
"link_doctype": "Party Specific Item",
"link_fieldname": "party"
}
],
"modified": "2021-08-27 18:02:44.314077",
"modified": "2021-09-06 17:37:56.522233",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",

View File

@@ -10,7 +10,7 @@ from frappe.contacts.address_and_contact import (
delete_contact_and_address,
load_address_and_contact,
)
from frappe.model.naming import set_name_by_naming_series
from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options
from erpnext.accounts.party import get_dashboard_info, validate_party_accounts
from erpnext.utilities.transaction_base import TransactionBase
@@ -40,8 +40,10 @@ class Supplier(TransactionBase):
supp_master_name = frappe.defaults.get_global_default('supp_master_name')
if supp_master_name == 'Supplier Name':
self.name = self.supplier_name
else:
elif supp_master_name == 'Naming Series':
set_name_by_naming_series(self)
else:
self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
def on_update(self):
if not self.naming_series:

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
class SupplierItemGroup(Document):
def validate(self):
exists = frappe.db.exists({
'doctype': 'Supplier Item Group',
'supplier': self.supplier,
'item_group': self.item_group
})
if exists:
frappe.throw(_("Item Group has already been linked to this supplier."))

View File

@@ -0,0 +1,77 @@
{
"creation": "2021-07-28 11:51:42.319984",
"docstatus": 0,
"doctype": "Form Tour",
"idx": 0,
"is_standard": 1,
"modified": "2021-10-05 13:06:56.414584",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
"owner": "Administrator",
"reference_doctype": "Buying Settings",
"save_on_complete": 0,
"steps": [
{
"description": "When a Supplier is saved, system generates a unique identity or name for that Supplier which can be used to refer the Supplier in various Buying transactions.",
"field": "",
"fieldname": "supp_master_name",
"fieldtype": "Select",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Supplier Naming By",
"parent_field": "",
"position": "Bottom",
"title": "Supplier Naming By"
},
{
"description": "Configure what should be the default value of Supplier Group when creating a new Supplier.",
"field": "",
"fieldname": "supplier_group",
"fieldtype": "Link",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Default Supplier Group",
"parent_field": "",
"position": "Right",
"title": "Default Supplier Group"
},
{
"description": "Item prices will be fetched from this Price List.",
"field": "",
"fieldname": "buying_price_list",
"fieldtype": "Link",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Default Buying Price List",
"parent_field": "",
"position": "Bottom",
"title": "Default Buying Price List"
},
{
"description": "If this option is configured \"Yes\", ERPNext will prevent you from creating a Purchase Invoice or a Purchase Receipt directly without creating a Purchase Order first.",
"field": "",
"fieldname": "po_required",
"fieldtype": "Select",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
"parent_field": "",
"position": "Bottom",
"title": "Purchase Order Required"
},
{
"description": "If this option is configured \"Yes\", ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first.",
"field": "",
"fieldname": "pr_required",
"fieldtype": "Select",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
"parent_field": "",
"position": "Bottom",
"title": "Purchase Receipt Required"
}
],
"title": "Buying Settings"
}

View File

@@ -0,0 +1,82 @@
{
"creation": "2021-07-29 14:11:58.271113",
"docstatus": 0,
"doctype": "Form Tour",
"idx": 0,
"is_standard": 1,
"modified": "2021-10-05 13:11:31.436135",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
"owner": "Administrator",
"reference_doctype": "Purchase Order",
"save_on_complete": 1,
"steps": [
{
"description": "Select a Supplier",
"field": "",
"fieldname": "supplier",
"fieldtype": "Link",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Supplier",
"parent_field": "",
"position": "Right",
"title": "Supplier"
},
{
"description": "Set the \"Required By\" date for the materials. This sets the \"Required By\" date for all the items.",
"field": "",
"fieldname": "schedule_date",
"fieldtype": "Date",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Required By",
"parent_field": "",
"position": "Left",
"title": "Required By"
},
{
"description": "Items to be purchased can be added here.",
"field": "",
"fieldname": "items",
"fieldtype": "Table",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Items",
"parent_field": "",
"position": "Bottom",
"title": "Items Table"
},
{
"child_doctype": "Purchase Order Item",
"description": "Enter the Item Code.",
"field": "",
"fieldname": "item_code",
"fieldtype": "Link",
"has_next_condition": 1,
"is_table_field": 1,
"label": "Item Code",
"next_step_condition": "eval: doc.item_code",
"parent_field": "",
"parent_fieldname": "items",
"position": "Right",
"title": "Item Code"
},
{
"child_doctype": "Purchase Order Item",
"description": "Enter the required quantity for the material.",
"field": "",
"fieldname": "qty",
"fieldtype": "Float",
"has_next_condition": 0,
"is_table_field": 1,
"label": "Quantity",
"parent_field": "",
"parent_fieldname": "items",
"position": "Bottom",
"title": "Quantity"
}
],
"title": "Purchase Order"
}

View File

@@ -19,7 +19,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying",
"idx": 0,
"is_complete": 0,
"modified": "2020-07-08 14:05:28.273641",
"modified": "2021-08-24 18:13:42.463776",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",
@@ -28,23 +28,11 @@
{
"step": "Introduction to Buying"
},
{
"step": "Create a Supplier"
},
{
"step": "Setup your Warehouse"
},
{
"step": "Create a Product"
},
{
"step": "Create a Material Request"
},
{
"step": "Create your first Purchase Order"
},
{
"step": "Buying Settings"
}
],
"subtitle": "Products, Purchases, Analysis, and more.",

View File

@@ -1,19 +1,21 @@
{
"action": "Create Entry",
"action": "Show Form Tour",
"action_label": "Let\u2019s create your first Material Request",
"creation": "2020-05-15 14:39:09.818764",
"description": "# Track Material Request\n\n\nAlso known as Purchase Request or an Indent, is a document identifying a requirement of a set of items (products or services) for various purposes like procurement, transfer, issue, or manufacturing. Once the Material Request is validated, a purchase manager can take the next actions for purchasing items like requesting RFQ from a supplier or directly placing an order with an identified Supplier.\n\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 1,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-15 14:39:09.818764",
"modified": "2021-08-24 18:08:08.347501",
"modified_by": "Administrator",
"name": "Create a Material Request",
"owner": "Administrator",
"reference_document": "Material Request",
"show_form_tour": 1,
"show_full_form": 1,
"title": "Create a Material Request",
"title": "Track Material Request",
"validate_action": 1
}

View File

@@ -1,19 +1,21 @@
{
"action": "Create Entry",
"action": "Show Form Tour",
"action_label": "Let\u2019s create your first Purchase Order",
"creation": "2020-05-12 18:17:49.976035",
"description": "# Create first Purchase Order\n\nPurchase Order is at the heart of your buying transactions. In ERPNext, Purchase Order can can be created against a Purchase Material Request (indent) and Supplier Quotation as well. Purchase Orders is also linked to Purchase Receipt and Purchase Invoices, allowing you to keep a birds-eye view on your purchase deals.\n\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-12 18:31:56.856112",
"modified": "2021-08-24 18:08:08.936484",
"modified_by": "Administrator",
"name": "Create your first Purchase Order",
"owner": "Administrator",
"reference_document": "Purchase Order",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Create your first Purchase Order",
"title": "Create first Purchase Order",
"validate_action": 1
}

View File

@@ -1,19 +1,22 @@
{
"action": "Watch Video",
"action": "Show Form Tour",
"action_label": "Let\u2019s walk-through few Buying Settings",
"creation": "2020-05-06 15:37:09.477765",
"description": "# Buying Settings\n\n\nBuying module\u2019s features are highly configurable as per your business needs. Buying Settings is the place where you can set your preferences for:\n\n- Supplier naming and default values\n- Billing and shipping preference in buying transactions\n\n\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_single": 1,
"is_skipped": 0,
"modified": "2020-05-12 18:25:08.509900",
"modified": "2021-08-24 18:08:08.345735",
"modified_by": "Administrator",
"name": "Introduction to Buying",
"owner": "Administrator",
"show_full_form": 0,
"title": "Introduction to Buying",
"reference_document": "Buying Settings",
"show_form_tour": 1,
"show_full_form": 1,
"title": "Buying Settings",
"validate_action": 1,
"video_url": "https://youtu.be/efFajTTQBa8"
}

View File

@@ -45,7 +45,6 @@ class TestProcurementTracker(unittest.TestCase):
pr = make_purchase_receipt(po.name)
pr.get("items")[0].cost_center = "Main - _TPC"
pr.submit()
frappe.db.commit()
date_obj = datetime.date(datetime.now())
po.load_from_db()

View File

@@ -30,7 +30,14 @@ frappe.query_reports["Purchase Order Analysis"] = {
"default": frappe.datetime.get_today()
},
{
"fieldname": "purchase_order",
"fieldname":"project",
"label": __("Project"),
"fieldtype": "Link",
"width": "80",
"options": "Project"
},
{
"fieldname": "name",
"label": __("Purchase Order"),
"fieldtype": "Link",
"width": "80",

View File

@@ -41,14 +41,12 @@ def get_conditions(filters):
if filters.get("from_date") and filters.get("to_date"):
conditions += " and po.transaction_date between %(from_date)s and %(to_date)s"
if filters.get("company"):
conditions += " and po.company = %(company)s"
for field in ['company', 'name', 'status']:
if filters.get(field):
conditions += f" and po.{field} = %({field})s"
if filters.get("purchase_order"):
conditions += " and po.name = %(purchase_order)s"
if filters.get("status"):
conditions += " and po.status in %(status)s"
if filters.get('project'):
conditions += " and poi.project = %(project)s"
return conditions
@@ -57,6 +55,7 @@ def get_data(conditions, filters):
SELECT
po.transaction_date as date,
poi.schedule_date as required_date,
poi.project,
po.name as purchase_order,
po.status, po.supplier, poi.item_code,
poi.qty, poi.received_qty,
@@ -175,6 +174,12 @@ def get_columns(filters):
"fieldtype": "Link",
"options": "Supplier",
"width": 130
},{
"label": _("Project"),
"fieldname": "project",
"fieldtype": "Link",
"options": "Project",
"width": 130
}]
if not filters.get("group_by_po"):

View File

@@ -0,0 +1,43 @@
# Version 13.12.0 Release Notes
### Features & Enhancements
- Merge POS invoices based on customer group ([#27471](https://github.com/frappe/erpnext/pull/27471))
- Get items from material request in purchase order ([#24725](https://github.com/frappe/erpnext/pull/24725))
- Earlier system was fetching all the items from material request to purchase order
- Now user can fetch the specific items from material request to purchase order
- Validity dates in Tax Withholding Rates ([#27258](https://github.com/frappe/erpnext/pull/27258))
- Replaced fiscal year with From Date and To Date, to start the TDS effect from the mid of the fiscal year.
- Toggle for reduced depreciation rate as per IT Act ([#27600](https://github.com/frappe/erpnext/pull/27600))
- Added a toggle in the Finance Book to enable/disable the automatic reduction in the depreciation rate.
- Deduct the TDS using Journal Entry ([#27451](https://github.com/frappe/erpnext/pull/27451))
- Refactored TDS payable monthly report to show the TDS data which was created using Journal Entry.
- Party specific item ([#27281](https://github.com/frappe/erpnext/pull/27281))
- User can set the specific items to the supplier
- While making purchase transactions, user can see the items which are linked to the respective supplier.
- Provision to add scrap items in job card ([#27483](https://github.com/frappe/erpnext/pull/27483))
### Fixes
- Maintain same rate in Stock Ledger until stock become positive ([#27227](https://github.com/frappe/erpnext/pull/27227))
- Duplicate Contact error on add Patient ([#27427](https://github.com/frappe/erpnext/pull/27427))
- Distribution of additional costs in Manufacture Stock Entry ([#27629](https://github.com/frappe/erpnext/pull/27629))
- Website Items with same Item name unhandled, thumbnails missing ([#27720](https://github.com/frappe/erpnext/pull/27720))
- Delivery Note for transfer w/o internal customer ([#27798](https://github.com/frappe/erpnext/pull/27798))
- Setting of gain/loss if party account is in company currency ([#27659](https://github.com/frappe/erpnext/pull/27659))
- Check if doctype has company_address field before setting the value ([#27441](https://github.com/frappe/erpnext/pull/27441))
- Removed b2c limit check from CDNR Invoices ([#27516](https://github.com/frappe/erpnext/pull/27516))
- Cannot delete a project if linked with sales order ([#27536](https://github.com/frappe/erpnext/pull/27536))
- Employee advance return through multiple additional salaries ([#27438](https://github.com/frappe/erpnext/pull/27438))
- Improvements in COA Importer ([#27584](https://github.com/frappe/erpnext/pull/27584))
- Validate if item exists on uploading items in stock reco ([#27543](https://github.com/frappe/erpnext/pull/27543))
- Handle Excess/Multiple Item Transfer against Job Card ([#27486](https://github.com/frappe/erpnext/pull/27486))
- Values with same account name and different account number in consolidated balance sheet report ([#27493](https://github.com/frappe/erpnext/pull/27493))
- Added project name in the purchase order analysis ([#27701](https://github.com/frappe/erpnext/pull/27701))
- Shopping Cart and Variant Selection ([#27508](https://github.com/frappe/erpnext/pull/27508))

View File

@@ -0,0 +1,29 @@
# Version 13.13.0 Release Notes
### Features & Enhancements
- HR Module onboarding ([#25741](https://github.com/frappe/erpnext/pull/25741))
- Tracking multiple rounds for the interview ([#25482](https://github.com/frappe/erpnext/pull/25482))
- HSN based tax breakup table check in GST Settings (India Localization) ([#27907](https://github.com/frappe/erpnext/pull/27907))
### Fixes
- To improve stock transactions added indexes in stock queries and speed up bin updation ([#27758](https://github.com/frappe/erpnext/pull/27758))
- Interstate internal transfer invoices not visible in GSTR-1 ([#27970](https://github.com/frappe/erpnext/pull/27970))
- Account number and name incorrectly imported using COA importer ([#27967](https://github.com/frappe/erpnext/pull/27967))
- Multiple fixes to timesheets ([#27775](https://github.com/frappe/erpnext/pull/27742))
- Totals row incorrect value in GL Entry ([#27867](https://github.com/frappe/erpnext/pull/27867))
- Sales Order delivery Date not getting set via data import ([#27862](https://github.com/frappe/erpnext/pull/27862))
- Add cost center in gl entry for advance payment entry ([#27840](https://github.com/frappe/erpnext/pull/27840))
- Item Variant selection empty popup on website ([#27924](https://github.com/frappe/erpnext/pull/27924))
- Improve performance of fetching account balance in chart of accounts ([#27661](https://github.com/frappe/erpnext/pull/27661))
- Chart Of Accounts import button not visible ([#27748](https://github.com/frappe/erpnext/pull/27748))
- Website Items with same Item name unhandled, thumbnails missing ([#27720](https://github.com/frappe/erpnext/pull/27720))
- Delete linked Transaction Deletion Record docs on deleting company ([#27785](https://github.com/frappe/erpnext/pull/27785))
- Display appropriate message for Payment Term discrepancies in Payment Entry ([#27749](https://github.com/frappe/erpnext/pull/27749))
- Updated buying onboarding tours. ([#27800](https://github.com/frappe/erpnext/pull/27800))
- Fixed variant qty in BOM while making work order ([#27686](https://github.com/frappe/erpnext/pull/27686))
- Availability slots display, disabled Practitioner Schedule ([#27812](https://github.com/frappe/erpnext/pull/27812))
- Consolidated report not consider company currency ([#27863](https://github.com/frappe/erpnext/pull/27863))
- Batch Number not copied from Purchase Receipt to Stock Entry ([#27794](https://github.com/frappe/erpnext/pull/27794))
- Employee Leave Balance report should only consider ledgers of transaction type Leave Allocation ([#27728](https://github.com/frappe/erpnext/pull/27728))

View File

@@ -690,13 +690,17 @@ class AccountsController(TransactionBase):
.format(d.reference_name, d.against_order))
def set_advance_gain_or_loss(self):
if not self.get("advances"):
if self.get('conversion_rate') == 1 or not self.get("advances"):
return
is_purchase_invoice = self.doctype == 'Purchase Invoice'
party_account = self.credit_to if is_purchase_invoice else self.debit_to
if get_account_currency(party_account) != self.currency:
return
for d in self.get("advances"):
advance_exchange_rate = d.ref_exchange_rate
if (d.allocated_amount and self.conversion_rate != 1
and self.conversion_rate != advance_exchange_rate):
if (d.allocated_amount and self.conversion_rate != advance_exchange_rate):
base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount
@@ -715,7 +719,7 @@ class AccountsController(TransactionBase):
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
if not gain_loss_account:
frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}")
frappe.throw(_("Please set default Exchange Gain/Loss Account in Company {}")
.format(self.get('company')))
account_currency = get_account_currency(gain_loss_account)
if account_currency != self.company_currency:
@@ -734,7 +738,7 @@ class AccountsController(TransactionBase):
"against": party,
dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
dr_or_cr: abs(d.exchange_gain_loss),
"cost_center": self.cost_center,
"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
"project": self.project
}, item=d)
)
@@ -985,42 +989,55 @@ class AccountsController(TransactionBase):
item_allowance = {}
global_qty_allowance, global_amount_allowance = None, None
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
user_roles = frappe.get_roles()
total_overbilled_amt = 0.0
for item in self.get("items"):
if item.get(item_ref_dn):
ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
item.get(item_ref_dn), based_on), self.precision(based_on, item))
if not ref_amt:
frappe.msgprint(
_("Warning: System will not check overbilling since amount for Item {0} in {1} is zero")
.format(item.item_code, ref_dt))
else:
already_billed = frappe.db.sql("""
select sum(%s)
from `tab%s`
where %s=%s and docstatus=1 and parent != %s
""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
(item.get(item_ref_dn), self.name))[0][0]
if not item.get(item_ref_dn):
continue
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
self.precision(based_on, item))
ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
item.get(item_ref_dn), based_on), self.precision(based_on, item))
if not ref_amt:
frappe.msgprint(
_("System will not check overbilling since amount for Item {0} in {1} is zero")
.format(item.item_code, ref_dt), title=_("Warning"), indicator="orange")
continue
allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
already_billed = frappe.db.sql("""
select sum(%s)
from `tab%s`
where %s=%s and docstatus=1 and parent != %s
""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
(item.get(item_ref_dn), self.name))[0][0]
max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
self.precision(based_on, item))
if total_billed_amt < 0 and max_allowed_amt < 0:
# while making debit note against purchase return entry(purchase receipt) getting overbill error
total_billed_amt = abs(total_billed_amt)
max_allowed_amt = abs(max_allowed_amt)
allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
if self.doctype != "Purchase Invoice":
self.throw_overbill_exception(item, max_allowed_amt)
elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
self.throw_overbill_exception(item, max_allowed_amt)
if total_billed_amt < 0 and max_allowed_amt < 0:
# while making debit note against purchase return entry(purchase receipt) getting overbill error
total_billed_amt = abs(total_billed_amt)
max_allowed_amt = abs(max_allowed_amt)
overbill_amt = total_billed_amt - max_allowed_amt
total_overbilled_amt += overbill_amt
if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles:
if self.doctype != "Purchase Invoice":
self.throw_overbill_exception(item, max_allowed_amt)
elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
self.throw_overbill_exception(item, max_allowed_amt)
if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1:
frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
.format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange")
def throw_overbill_exception(self, item, max_allowed_amt):
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
@@ -1673,14 +1690,59 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
return list(payment_entries_against_order) + list(unallocated_payment_entries)
def update_invoice_status():
# Daily update the status of the invoices
frappe.db.sql(""" update `tabSales Invoice` set status = 'Overdue'
where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
frappe.db.sql(""" update `tabPurchase Invoice` set status = 'Overdue'
where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
"""Updates status as Overdue for applicable invoices. Runs daily."""
today = getdate()
for doctype in ("Sales Invoice", "Purchase Invoice"):
frappe.db.sql("""
UPDATE `tab{doctype}` invoice SET invoice.status = 'Overdue'
WHERE invoice.docstatus = 1
AND invoice.status REGEXP '^Unpaid|^Partly Paid'
AND invoice.outstanding_amount > 0
AND (
{or_condition}
(
(
CASE
WHEN invoice.party_account_currency = invoice.currency
THEN (
CASE
WHEN invoice.disable_rounded_total
THEN invoice.grand_total
ELSE invoice.rounded_total
END
)
ELSE (
CASE
WHEN invoice.disable_rounded_total
THEN invoice.base_grand_total
ELSE invoice.base_rounded_total
END
)
END
) - invoice.outstanding_amount
) < (
SELECT SUM(
CASE
WHEN invoice.party_account_currency = invoice.currency
THEN ps.payment_amount
ELSE ps.base_payment_amount
END
)
FROM `tabPayment Schedule` ps
WHERE ps.parent = invoice.name
AND ps.due_date < %(today)s
)
)
""".format(
doctype=doctype,
or_condition=(
"invoice.is_pos AND invoice.due_date < %(today)s OR"
if doctype == "Sales Invoice"
else ""
)
), {"today": today}
)
@frappe.whitelist()
def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):

View File

@@ -7,6 +7,7 @@ import json
from collections import defaultdict
import frappe
from frappe import scrub
from frappe.desk.reportview import get_filters_cond, get_match_cond
from frappe.utils import nowdate, unique
@@ -223,18 +224,29 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
if not field in searchfields]
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
if filters and isinstance(filters, dict) and filters.get('supplier'):
item_group_list = frappe.get_all('Supplier Item Group',
filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
if filters and isinstance(filters, dict):
if filters.get('customer') or filters.get('supplier'):
party = filters.get('customer') or filters.get('supplier')
item_rules_list = frappe.get_all('Party Specific Item',
filters = {'party': party}, fields = ['restrict_based_on', 'based_on_value'])
item_groups = []
for i in item_group_list:
item_groups.append(i.item_group)
filters_dict = {}
for rule in item_rules_list:
if rule['restrict_based_on'] == 'Item':
rule['restrict_based_on'] = 'name'
filters_dict[rule.restrict_based_on] = []
del filters['supplier']
for rule in item_rules_list:
filters_dict[rule.restrict_based_on].append(rule.based_on_value)
for filter in filters_dict:
filters[scrub(filter)] = ['in', filters_dict[filter]]
if filters.get('customer'):
del filters['customer']
else:
del filters['supplier']
if item_groups:
filters['item_group'] = ['in', item_groups]
description_cond = ''
if frappe.db.count('Item', cache=True) < 50000:
@@ -307,7 +319,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
@frappe.validate_and_sanitize_search_inputs
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
cond = ''
if filters.get('customer'):
if filters and filters.get('customer'):
cond = """(`tabProject`.customer = %s or
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))

View File

@@ -424,7 +424,7 @@ class SellingController(StockController):
or (cint(self.is_return) and self.docstatus==2)):
sl_entries.append(self.get_sle_for_source_warehouse(d))
if d.target_warehouse and self.get("is_internal_customer"):
if d.target_warehouse:
sl_entries.append(self.get_sle_for_target_warehouse(d))
if d.warehouse and ((not cint(self.is_return) and self.docstatus==2)
@@ -559,6 +559,12 @@ class SellingController(StockController):
frappe.throw(_("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same")
.format(d.idx, warehouse, warehouse))
if not self.get("is_internal_customer") and any(d.get("target_warehouse") for d in items):
msg = _("Target Warehouse is set for some items but the customer is not an internal customer.")
msg += " " + _("This {} will be treated as material transfer.").format(_(self.doctype))
frappe.msgprint(msg, title="Internal Transfer", alert=True)
def validate_items(self):
# validate items to see if they have is_sales_item enabled
from erpnext.controllers.buying_controller import validate_item_type

View File

@@ -79,8 +79,15 @@ class StockController(AccountsController):
def clean_serial_nos(self):
for row in self.get("items"):
if hasattr(row, "serial_no") and row.serial_no:
# replace commas by linefeed and remove all spaces in string
row.serial_no = row.serial_no.replace(",", "\n").replace(" ", "")
# replace commas by linefeed
row.serial_no = row.serial_no.replace(",", "\n")
# strip preceeding and succeeding spaces for each SN
# (SN could have valid spaces in between e.g. SN - 123 - 2021)
serial_no_list = row.serial_no.split("\n")
serial_no_list = [sn.strip() for sn in serial_no_list]
row.serial_no = "\n".join(serial_no_list)
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None):
@@ -591,7 +598,7 @@ def future_sle_exists(args, sl_entries=None):
data = frappe.db.sql("""
select item_code, warehouse, count(name) as total_row
from `tabStock Ledger Entry`
from `tabStock Ledger Entry` force index (item_warehouse)
where
({})
and timestamp(posting_date, posting_time)

View File

@@ -0,0 +1,87 @@
import unittest
from functools import partial
from erpnext.controllers import queries
def add_default_params(func, doctype):
return partial(
func, doctype=doctype, txt="", searchfield="name", start=0, page_len=20, filters=None
)
class TestQueries(unittest.TestCase):
# All tests are based on doctype/test_records.json
def assert_nested_in(self, item, container):
self.assertIn(item, [vals for tuples in container for vals in tuples])
def test_employee_query(self):
query = add_default_params(queries.employee_query, "Employee")
self.assertGreaterEqual(len(query(txt="_Test Employee")), 3)
self.assertGreaterEqual(len(query(txt="_Test Employee 1")), 1)
def test_lead_query(self):
query = add_default_params(queries.lead_query, "Lead")
self.assertGreaterEqual(len(query(txt="_Test Lead")), 4)
self.assertEqual(len(query(txt="_Test Lead 4")), 1)
def test_customer_query(self):
query = add_default_params(queries.customer_query, "Customer")
self.assertGreaterEqual(len(query(txt="_Test Customer")), 7)
self.assertGreaterEqual(len(query(txt="_Test Customer USD")), 1)
def test_supplier_query(self):
query = add_default_params(queries.supplier_query, "Supplier")
self.assertGreaterEqual(len(query(txt="_Test Supplier")), 7)
self.assertGreaterEqual(len(query(txt="_Test Supplier USD")), 1)
def test_item_query(self):
query = add_default_params(queries.item_query, "Item")
self.assertGreaterEqual(len(query(txt="_Test Item")), 7)
self.assertEqual(len(query(txt="_Test Item Home Desktop 100 3")), 1)
fg_item = "_Test FG Item"
stock_items = query(txt=fg_item, filters={"is_stock_item": 1})
self.assert_nested_in("_Test FG Item", stock_items)
bundled_stock_items = query(txt="_test product bundle item 5", filters={"is_stock_item": 1})
self.assertEqual(len(bundled_stock_items), 0)
def test_bom_qury(self):
query = add_default_params(queries.bom, "BOM")
self.assertGreaterEqual(len(query(txt="_Test Item Home Desktop Manufactured")), 1)
def test_project_query(self):
query = add_default_params(queries.get_project_name, "BOM")
self.assertGreaterEqual(len(query(txt="_Test Project")), 1)
def test_account_query(self):
query = add_default_params(queries.get_account_list, "Account")
debtor_accounts = query(txt="Debtors", filters={"company": "_Test Company"})
self.assert_nested_in("Debtors - _TC", debtor_accounts)
def test_income_account_query(self):
query = add_default_params(queries.get_income_account, "Account")
self.assertGreaterEqual(len(query(filters={"company": "_Test Company"})), 1)
def test_expense_account_query(self):
query = add_default_params(queries.get_expense_account, "Account")
self.assertGreaterEqual(len(query(filters={"company": "_Test Company"})), 1)
def test_warehouse_query(self):
query = add_default_params(queries.warehouse_query, "Account")
wh = query(filters=[["Bin", "item_code", "=", "_Test Item"]])
self.assertGreaterEqual(len(wh), 1)

View File

@@ -7,6 +7,7 @@ import json
import frappe
from frappe import _
from frappe.modules.utils import get_module_app
from frappe.utils import flt, has_common
from frappe.utils.user import is_website_user
@@ -21,8 +22,32 @@ def get_list_context(context=None):
"get_list": get_transaction_list
}
def get_webform_list_context(module):
if get_module_app(module) != 'erpnext':
return
return {
"get_list": get_webform_transaction_list
}
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
""" Get List of transactions for custom doctypes """
from frappe.www.list import get_list
if not filters:
filters = []
meta = frappe.get_meta(doctype)
for d in meta.fields:
if d.fieldtype == 'Link' and d.fieldname != 'amended_from':
allowed_docs = [d.name for d in get_transaction_list(doctype=d.options, custom=True)]
allowed_docs.append('')
filters.append((d.fieldname, 'in', allowed_docs))
return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=False,
fields=None, order_by="modified")
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified", custom=False):
user = frappe.session.user
ignore_permissions = False
@@ -46,7 +71,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
filters.append(('customer', 'in', customers))
elif suppliers:
filters.append(('supplier', 'in', suppliers))
else:
elif not custom:
return []
if doctype == 'Request for Quotation':
@@ -56,9 +81,16 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
# Since customers and supplier do not have direct access to internal doctypes
ignore_permissions = True
if not customers and not suppliers and custom:
ignore_permissions = False
filters = []
transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
if custom:
return transactions
return post_process(doctype, transactions)
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,

View File

@@ -34,6 +34,7 @@ class Opportunity(TransactionBase):
self.validate_item_details()
self.validate_uom_is_integer("uom", "qty")
self.validate_cust_name()
self.map_fields()
if not self.title:
self.title = self.customer_name
@@ -41,6 +42,15 @@ class Opportunity(TransactionBase):
if not self.with_items:
self.items = []
def map_fields(self):
for field in self.meta.fields:
if not self.get(field.fieldname):
try:
value = frappe.db.get_value(self.opportunity_from, self.party_name, field.fieldname)
frappe.db.set(self, field.fieldname, value)
except Exception:
continue
def make_new_lead_if_required(self):
"""Set lead against new opportunity"""
if (not self.get("party_name")) and self.contact_email:

View File

@@ -41,7 +41,6 @@ class TestECommerceSettings(unittest.TestCase):
def test_tax_rule_validation(self):
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
frappe.db.commit()
cart_settings = self.get_cart_settings()
cart_settings.enabled = 1

View File

@@ -33,6 +33,16 @@ class WebsiteItem(WebsiteGenerator):
no_cache=1
)
def autoname(self):
# use naming series to accomodate items with same name (different item code)
from frappe.model.naming import make_autoname
from erpnext.setup.doctype.naming_series.naming_series import get_default_naming_series
naming_series = get_default_naming_series("Website Item")
if not self.name and naming_series:
self.name = make_autoname(naming_series, doc=self)
def onload(self):
super(WebsiteItem, self).onload()
@@ -210,7 +220,7 @@ class WebsiteItem(WebsiteGenerator):
self.get_product_details_section(context)
if settings.enable_reviews:
if settings.get("enable_reviews"):
reviews_data = get_item_reviews(self.name)
context.update(reviews_data)
context.reviews = context.reviews[:4]

View File

@@ -23,7 +23,11 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
cart_settings = get_shopping_cart_settings()
if not cart_settings.enabled:
return frappe._dict()
# return settings even if cart is disabled
return frappe._dict({
"product_info": {},
"cart_settings": cart_settings
})
cart_quotation = frappe._dict()
if not skip_quotation_creation:

View File

@@ -1,7 +1,7 @@
{%- macro slide(image, title, subtitle, action, label, index, align="Left", theme="Dark") -%}
{%- set align_class = resolve_class({
'text-right': align == 'Right',
'text-centre': align == 'Center',
'text-centre': align == 'Centre',
'text-left': align == 'Left',
}) -%}
@@ -15,7 +15,7 @@
<div class="carousel-body container d-flex {{ align_class }}">
<div class="carousel-content align-self-center">
{%- if title -%}<h1 class="{{ heading_class }}">{{ title }}</h1>{%- endif -%}
{%- if subtitle -%}<p class="text-muted mt-2">{{ subtitle }}</p>{%- endif -%}
{%- if subtitle -%}<p class="{{ heading_class }} mt-2">{{ subtitle }}</p>{%- endif -%}
{%- if action -%}
<a href="{{ action }}" class="btn btn-primary mt-3">
{{ label }}
@@ -27,12 +27,14 @@
</div>
{%- endmacro -%}
<div id="{{ slider_name }}" class="section-carousel carousel slide" data-ride="carousel">
{%- set hero_slider_id = 'id-' + frappe.utils.generate_hash('HeroSlider', 12) -%}
<div id="{{ hero_slider_id }}" class="section-carousel carousel slide" data-ride="carousel">
{%- if show_indicators -%}
<ol class="carousel-indicators">
{%- for index in ['1', '2', '3', '4', '5'] -%}
{%- if values['slide_' + index + '_image'] -%}
<li data-target="#{{ slider_name }}" data-slide-to="{{ frappe.utils.cint(index) - 1 }}" class="{{ 'active' if index=='1' else ''}}"></li>
<li data-target="#{{ hero_slider_id }}" data-slide-to="{{ frappe.utils.cint(index) - 1 }}" class="{{ 'active' if index=='1' else ''}}"></li>
{%- endif -%}
{%- endfor -%}
</ol>
@@ -54,7 +56,7 @@
{%- endfor -%}
</div>
{%- if show_controls -%}
<a class="carousel-control-prev" href="#{{ slider_name }}" role="button" data-slide="prev">
<a class="carousel-control-prev" href="#{{ hero_slider_id }}" role="button" data-slide="prev">
<div class="carousel-control">
<svg class="mr-1" width="20" height="20" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.625 3.75L6.375 9L11.625 14.25" stroke="#4C5A67" stroke-linecap="round" stroke-linejoin="round"/>
@@ -62,7 +64,7 @@
</div>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#{{ slider_name }}" role="button" data-slide="next">
<a class="carousel-control-next" href="#{{ hero_slider_id }}" role="button" data-slide="next">
<div class="carousel-control">
<svg class="ml-1" width="20" height="20" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.375 14.25L11.625 9L6.375 3.75" stroke="#4C5A67" stroke-linecap="round" stroke-linejoin="round"/>
@@ -73,13 +75,12 @@
{%- endif -%}
</div>
<script type="text/javascript">
$('.carousel').carousel({
interval: false,
pause: "hover",
wrap: true
})
<script>
frappe.ready(function () {
$('.carousel').carousel({
interval: false,
pause: "hover",
wrap: true
})
});
</script>
<style>
</style>

View File

@@ -138,7 +138,9 @@ class Student(Document):
enrollment.submit()
return enrollment
def enroll_in_course(self, course_name, program_enrollment, enrollment_date=frappe.utils.datetime.datetime.now()):
def enroll_in_course(self, course_name, program_enrollment, enrollment_date=None):
if enrollment_date is None:
enrollment_date = frappe.utils.datetime.datetime.now()
try:
enrollment = frappe.get_doc({
"doctype": "Course Enrollment",

View File

@@ -85,10 +85,8 @@ def add_bank_accounts(response, bank, company):
if not acc_subtype:
add_account_subtype(account["subtype"])
existing_bank_account = frappe.db.exists("Bank Account", {
'account_name': account["name"],
'bank': bank["bank_name"]
})
bank_account_name = "{} - {}".format(account["name"], bank["bank_name"])
existing_bank_account = frappe.db.exists("Bank Account", bank_account_name)
if not existing_bank_account:
try:
@@ -197,6 +195,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
plaid = PlaidConnector(access_token)
transactions = []
try:
transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
except ItemError as e:
@@ -205,7 +204,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
return transactions or []
return transactions
def new_bank_transaction(transaction):

View File

@@ -62,7 +62,9 @@ class InpatientRecord(Document):
admit_patient(self, service_unit, check_in, expected_discharge)
@frappe.whitelist()
def discharge(self, check_out=now_datetime()):
def discharge(self, check_out=None):
if not check_out:
check_out = now_datetime()
if (getdate(check_out) < getdate(self.admitted_datetime)):
frappe.throw(_('Discharge date cannot be less than Admission date'))
discharge_patient(self, check_out)

View File

@@ -40,7 +40,7 @@ class Patient(Document):
frappe.db.set_value('Patient', self.name, 'status', 'Disabled')
else:
send_registration_sms(self)
self.reload() # self.notify_update()
self.reload()
def on_update(self):
if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'):
@@ -93,23 +93,27 @@ class Patient(Document):
self.language = frappe.db.get_single_value('System Settings', 'language')
def create_website_user(self):
if self.email and not frappe.db.exists('User', self.email):
user = frappe.get_doc({
'doctype': 'User',
'first_name': self.first_name,
'last_name': self.last_name,
'email': self.email,
'user_type': 'Website User',
'gender': self.sex,
'phone': self.phone,
'mobile_no': self.mobile,
'birth_date': self.dob
})
user.flags.ignore_permissions = True
user.enabled = True
user.send_welcome_email = True
user.add_roles('Patient')
frappe.db.set_value(self.doctype, self.name, 'user_id', user.name)
users = frappe.db.get_all('User', fields=['email', 'mobile_no'], or_filters={'email': self.email, 'mobile_no': self.mobile})
if users and users[0]:
frappe.throw(_("User exists with Email {}, Mobile {}<br>Please check email / mobile or disable 'Invite as User' to skip creating User")
.format(frappe.bold(users[0].email), frappe.bold(users[0].mobile_no)), frappe.DuplicateEntryError)
user = frappe.get_doc({
'doctype': 'User',
'first_name': self.first_name,
'last_name': self.last_name,
'email': self.email,
'user_type': 'Website User',
'gender': self.sex,
'phone': self.phone,
'mobile_no': self.mobile,
'birth_date': self.dob
})
user.flags.ignore_permissions = True
user.enabled = True
user.send_welcome_email = True
user.add_roles('Patient')
self.db_set('user_id', user.name)
def autoname(self):
patient_name_by = frappe.db.get_single_value('Healthcare Settings', 'patient_name_by')
@@ -159,54 +163,65 @@ class Patient(Document):
return {'invoice': sales_invoice.name}
def set_contact(self):
if frappe.db.exists('Dynamic Link', {'parenttype':'Contact', 'link_doctype':'Patient', 'link_name':self.name}):
old_doc = self.get_doc_before_save()
if old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone:
self.update_contact()
else:
self.reload()
if self.email or self.mobile or self.phone:
contact = frappe.get_doc({
'doctype': 'Contact',
'first_name': self.first_name,
'middle_name': self.middle_name,
'last_name': self.last_name,
'gender': self.sex,
'is_primary_contact': 1
})
contact.append('links', dict(link_doctype='Patient', link_name=self.name))
if self.customer:
contact.append('links', dict(link_doctype='Customer', link_name=self.customer))
contact.insert(ignore_permissions=True)
self.update_contact(contact) # update email, mobile and phone
def update_contact(self, contact=None):
if not contact:
contact_name = get_default_contact(self.doctype, self.name)
if contact_name:
contact = frappe.get_doc('Contact', contact_name)
contact = get_default_contact(self.doctype, self.name)
if contact:
if self.email and self.email != contact.email_id:
for email in contact.email_ids:
email.is_primary = True if email.email_id == self.email else False
contact.add_email(self.email, is_primary=True)
contact.set_primary_email()
old_doc = self.get_doc_before_save()
if not old_doc:
return
if self.mobile and self.mobile != contact.mobile_no:
for mobile in contact.phone_nos:
mobile.is_primary_mobile_no = True if mobile.phone == self.mobile else False
contact.add_phone(self.mobile, is_primary_mobile_no=True)
contact.set_primary('mobile_no')
if old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone:
self.update_contact(contact)
else:
if self.customer:
# customer contact exists, link patient
contact = get_default_contact('Customer', self.customer)
if self.phone and self.phone != contact.phone:
for phone in contact.phone_nos:
phone.is_primary_phone = True if phone.phone == self.phone else False
contact.add_phone(self.phone, is_primary_phone=True)
contact.set_primary('phone')
if contact:
self.update_contact(contact)
else:
self.reload()
if self.email or self.mobile or self.phone:
contact = frappe.get_doc({
'doctype': 'Contact',
'first_name': self.first_name,
'middle_name': self.middle_name,
'last_name': self.last_name,
'gender': self.sex,
'is_primary_contact': 1
})
contact.append('links', dict(link_doctype='Patient', link_name=self.name))
if self.customer:
contact.append('links', dict(link_doctype='Customer', link_name=self.customer))
contact.flags.ignore_validate = True # disable hook TODO: safe?
contact.insert(ignore_permissions=True)
self.update_contact(contact.name)
def update_contact(self, contact):
contact = frappe.get_doc('Contact', contact)
if not contact.has_link(self.doctype, self.name):
contact.append('links', dict(link_doctype=self.doctype, link_name=self.name))
if self.email and self.email != contact.email_id:
for email in contact.email_ids:
email.is_primary = True if email.email_id == self.email else False
contact.add_email(self.email, is_primary=True)
contact.set_primary_email()
if self.mobile and self.mobile != contact.mobile_no:
for mobile in contact.phone_nos:
mobile.is_primary_mobile_no = True if mobile.phone == self.mobile else False
contact.add_phone(self.mobile, is_primary_mobile_no=True)
contact.set_primary('mobile_no')
if self.phone and self.phone != contact.phone:
for phone in contact.phone_nos:
phone.is_primary_phone = True if phone.phone == self.phone else False
contact.add_phone(self.phone, is_primary_phone=True)
contact.set_primary('phone')
contact.flags.skip_patient_update = True
contact.save(ignore_permissions=True)

View File

@@ -35,3 +35,40 @@ class TestPatient(unittest.TestCase):
settings.collect_registration_fee = 0
settings.save()
def test_patient_contact(self):
frappe.db.sql("""delete from `tabPatient` where name like '_Test Patient%'""")
frappe.db.sql("""delete from `tabCustomer` where name like '_Test Patient%'""")
frappe.db.sql("""delete from `tabContact` where name like'_Test Patient%'""")
frappe.db.sql("""delete from `tabDynamic Link` where parent like '_Test Patient%'""")
patient = create_patient(patient_name='_Test Patient Contact', email='test-patient@example.com', mobile='+91 0000000001')
customer = frappe.db.get_value('Patient', patient, 'customer')
self.assertTrue(customer)
self.assertTrue(frappe.db.exists('Dynamic Link', {'parenttype': 'Contact', 'link_doctype': 'Patient', 'link_name': patient}))
self.assertTrue(frappe.db.exists('Dynamic Link', {'parenttype': 'Contact', 'link_doctype': 'Customer', 'link_name': customer}))
# a second patient linking with same customer
new_patient = create_patient(email='test-patient@example.com', mobile='+91 0000000009', customer=customer)
self.assertTrue(frappe.db.exists('Dynamic Link', {'parenttype': 'Contact', 'link_doctype': 'Patient', 'link_name': new_patient}))
self.assertTrue(frappe.db.exists('Dynamic Link', {'parenttype': 'Contact', 'link_doctype': 'Customer', 'link_name': customer}))
def test_patient_user(self):
frappe.db.sql("""delete from `tabUser` where email='test-patient-user@example.com'""")
frappe.db.sql("""delete from `tabDynamic Link` where parent like '_Test Patient%'""")
frappe.db.sql("""delete from `tabPatient` where name like '_Test Patient%'""")
patient = create_patient(patient_name='_Test Patient User', email='test-patient-user@example.com', mobile='+91 0000000009', create_user=True)
user = frappe.db.get_value('Patient', patient, 'user_id')
self.assertTrue(frappe.db.exists('User', user))
new_patient = frappe.get_doc({
'doctype': 'Patient',
'first_name': '_Test Patient Duplicate User',
'sex': 'Male',
'email': 'test-patient-user@example.com',
'mobile': '+91 0000000009',
'invite_user': 1
})
self.assertRaises(frappe.exceptions.DuplicateEntryError, new_patient.insert)

View File

@@ -433,11 +433,12 @@ let check_and_set_availability = function(frm) {
slot_html += `<br><span> <b> ${__('Maximum Capacity:')} </b> ${slot_info.service_unit_capacity} </span>`;
}
slot_html += '</div><br><br>';
slot_html += '</div><br>';
slot_html += slot_info.avail_slot.map(slot => {
appointment_count = 0;
disabled = false;
count_class = tool_tip = '';
start_str = slot.from_time;
slot_start_time = moment(slot.from_time, 'HH:mm:ss');
slot_end_time = moment(slot.to_time, 'HH:mm:ss');
@@ -486,10 +487,11 @@ let check_and_set_availability = function(frm) {
data-duration=${interval}
data-service-unit="${slot_info.service_unit || ''}"
style="margin: 0 10px 10px 0; width: auto;" ${disabled ? 'disabled="disabled"' : ""}
data-toggle="tooltip" title="${tool_tip}">
${start_str.substring(0, start_str.length - 3)}<br>
<span class='badge ${count_class}'> ${count} </span>
data-toggle="tooltip" title="${tool_tip || ''}">
${start_str.substring(0, start_str.length - 3)}
${slot_info.service_unit_capacity ? `<br><span class='badge ${count_class}'> ${count} </span>` : ''}
</button>`;
}).join("");
if (slot_info.service_unit_capacity) {

View File

@@ -354,7 +354,7 @@ def get_available_slots(practitioner_doc, date):
validate_practitioner_schedules(schedule_entry, practitioner)
practitioner_schedule = frappe.get_doc('Practitioner Schedule', schedule_entry.schedule)
if practitioner_schedule:
if practitioner_schedule and not practitioner_schedule.disabled:
available_slots = []
for time_slot in practitioner_schedule.time_slots:
if weekday == time_slot.day:

View File

@@ -307,14 +307,18 @@ def create_healthcare_docs(id=0):
return patient, practitioner
def create_patient(id=0):
def create_patient(id=0, patient_name=None, email=None, mobile=None, customer=None, create_user=False):
if frappe.db.exists('Patient', {'firstname':f'_Test Patient {str(id)}'}):
patient = frappe.db.get_value('Patient', {'first_name': f'_Test Patient {str(id)}'}, ['name'])
return patient
patient = frappe.new_doc('Patient')
patient.first_name = f'_Test Patient {str(id)}'
patient.first_name = patient_name if patient_name else f'_Test Patient {str(id)}'
patient.sex = 'Female'
patient.mobile = mobile
patient.email = email
patient.customer = customer
patient.invite_user = create_user
patient.save(ignore_permissions=True)
return patient.name

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