Compare commits

..

424 Commits

Author SHA1 Message Date
Sahil Khan
7ff82fccf3 Merge branch 'develop' into version-12 2019-08-22 13:10:17 +05:30
Sahil Khan
cd25d6dc7f bumped to version 12.0.8 2019-08-22 13:30:17 +05:50
rohitwaghchaure
224d857928 Merge pull request #18703 from rohitwaghchaure/reconciled_entry_still_has_no_clearance_date_develop
fix: reconciled entry has not clearance date set
2019-08-21 23:36:06 +05:30
rohitwaghchaure
a23fc327df Merge pull request #18785 from rohitwaghchaure/payment_recon_for_debit_entry_not_working_properly_develop
fix: debit note not reconciled with another purchase invoice using pa…
2019-08-21 23:35:35 +05:30
Anastes Mp
a6c6e02c49 Incorrect database table (#18558)
Fixed Unknow Column tax_type error on offline pos
2019-08-21 16:53:06 +05:30
Suraj Shetty
109a07b834 fix: Fix get_employees_with_number query
if the passed number is 7039392929 then it should match 
with the employee cell_number set as `7039392929, 0288382222`
2019-08-21 15:19:08 +05:30
Mangesh-Khairnar
5619db28cb fix: fetch capital work in progress as expense account (#18780) 2019-08-21 14:49:24 +05:30
Nabin Hait
bcb0f6038e fix: Single gl entry should only be considered once either in opening or closing entry (#18792) 2019-08-21 14:47:33 +05:30
Deepesh Garg
e9e0c0e7d4 fix: Minor fix in GSTR-1 report (#18797) 2019-08-21 14:40:35 +05:30
Suraj Shetty
e84de50147 fix: check if number exists after striping '0' (#18805)
fix: check if number exists after striping '0'
2019-08-21 14:22:59 +05:30
Suraj Shetty
6affeaa9c1 fix: check if number exists after striping '0'
- Absract number striping logic to separate method
- Rename a confusing variable name
- Remove leftout print statement
2019-08-21 09:14:56 +05:30
rohitwaghchaure
d36ff39498 Merge pull request #18799 from scmmishra/cart-fix
fix: shopping cart item availability
2019-08-21 08:58:47 +05:30
Mangesh-Khairnar
576f51d89e Merge pull request #18791 from Mangesh-Khairnar/fix-ledger-entry
fix: ledger entries
2019-08-20 19:57:50 +05:30
Shivam Mishra
eab5be1110 fix: shopping cart item availability 2019-08-20 19:31:54 +05:30
Mangesh-Khairnar
c9b6c9bb61 fix: calculate opening leave balance 2019-08-20 19:18:45 +05:30
Mangesh-Khairnar
6d549b03f1 Merge branch 'develop' into fix-ledger-entry 2019-08-20 17:58:44 +05:30
Saurabh
128db172f0 Merge pull request #18796 from sahil28297/merge_v12_hotfix_1
Merge version-12-hotfix into develop
2019-08-20 17:49:24 +05:30
Sahil Khan
5f2c030ddc Merge branch 'version-12-hotfix' into merge_v12_hotfix_1 2019-08-20 16:22:49 +05:30
Sahil Khan
bf2b712f4b Merge branch 'version-12-hotfix' into version-12 2019-08-20 16:00:26 +05:30
Sahil Khan
a8403cde16 bumped to version 12.0.7 2019-08-20 16:20:26 +05:50
Mangesh-Khairnar
79414a8d36 fix: fetch employee name on leave ledger creation 2019-08-20 14:45:57 +05:30
Mangesh-Khairnar
5cdda19494 fix: ledger entries after expiry 2019-08-20 14:45:57 +05:30
rohitwaghchaure
16dda1a991 fix: group by voucher consolidated showing incorrect data for deferred entries (#18779) 2019-08-20 12:37:32 +05:30
rohitwaghchaure
d5b4b1fdaf fix: group by voucher consolidated showing incorrect data for deferred entries (#18777) 2019-08-20 12:35:15 +05:30
Mangesh-Khairnar
b69595904a Merge pull request #18786 from Mangesh-Khairnar/payment-order-fetch-fix
fix: restrict the payment order to non received type payment entries
2019-08-20 12:21:24 +05:30
rohitwaghchaure
9ef7d5edaf Merge pull request #18789 from rohitwaghchaure/asset_pro_rata_cal_for_wdv_remaining_amt_develop
Asset pro rata cal for wdv remaining amt develop
2019-08-20 10:33:15 +05:30
Rohit Waghchaure
5d3dee206f fixed test cases and the logic for pro rata calculation 2019-08-20 10:30:56 +05:30
Rohit Waghchaure
1583925080 fix: Pro rata calculation is not working for WDV depreciation method 2019-08-20 10:30:38 +05:30
Mangesh-Khairnar
c97a7fb845 fix: restrict the payment order to non received type payment entries 2019-08-20 00:27:43 +05:30
Mangesh-Khairnar
becb89213f Merge pull request #18737 from Mangesh-Khairnar/fix-payment-order
fix: restrict the payment order to non received type payment entries
2019-08-20 00:13:32 +05:30
Rohit Waghchaure
1008e6e450 fix: debit note not reconciled with another purchase invoice using payment reconciliation 2019-08-19 20:27:15 +05:30
Mangesh-Khairnar
09ddc84c3a Merge pull request #18774 from deepeshgarg007/test_case_fix
fix: Failing sales and purchase return test cases
2019-08-19 19:20:52 +05:30
Deepesh Garg
33b392ac2b Update erpnext/controllers/sales_and_purchase_return.py
Co-Authored-By: Mangesh-Khairnar <mkhairnar10@gmail.com>
2019-08-19 17:43:10 +05:30
Deepesh Garg
3965451c7a Update erpnext/controllers/sales_and_purchase_return.py
Co-Authored-By: Mangesh-Khairnar <mkhairnar10@gmail.com>
2019-08-19 17:40:29 +05:30
deepeshgarg007
0487ad5515 fix: Code cleanup 2019-08-19 16:40:29 +05:30
deepeshgarg007
e2acc748c8 fix: Check for return against delivery noteas well 2019-08-19 16:38:04 +05:30
Mangesh-Khairnar
4645727163 Merge branch 'develop' into fix-payment-order 2019-08-19 16:29:30 +05:30
deepeshgarg007
7d288437d8 fix: Assignment 2019-08-19 16:19:48 +05:30
deepeshgarg007
c80e5fe7a1 fix: Failing sales and purchase return test cases 2019-08-19 14:38:15 +05:30
Deepesh Garg
5efedd7a60 fix: Travis (#18772) 2019-08-19 12:56:22 +05:30
Deepesh Garg
960a1cbd8f fix: Party dashboard heatmap not capturing sales, purchase and other activities (#18753) 2019-08-19 11:51:16 +05:30
Anurag Mishra
19c3cb0d5b fix: removed filters(not required) (#18729) 2019-08-19 10:31:02 +05:30
Anurag Mishra
bc5712a1b3 fix: validated cost center in financial_statement (#18733)
* fix: validated cost center in financial_statement

* Update financial_statements.py
2019-08-19 10:24:44 +05:30
Mangesh-Khairnar
0df513434e fix: valuation rate in stock ledger (#18744)
* fix: valuation rate in stock ledger

* test: allow zero valuation rate for items
2019-08-19 10:04:52 +05:30
Karthikeyan S
d5e5e22adb Merge pull request #18523 from Vigneshsekar/attendance_grace_period
feat(Auto Attendance): Add grace period
2019-08-18 13:25:48 +05:30
Deepesh Garg
77ec3cf402 Merge pull request #18757 from deepeshgarg007/accounting_dimension_fix
fix: Default dimensions in child doctypes
2019-08-16 17:27:14 +05:30
deepeshgarg007
0a2ed6da37 fix: Add missing semicolon 2019-08-16 15:50:17 +05:30
Deepesh Garg
6e80cbfd1e Merge pull request #18761 from deepeshgarg007/symbol_fix_develop
fix: Symbol fix in chart of accounts
2019-08-16 15:48:17 +05:30
Faris Ansari
ac3579d2c6 Merge pull request #17624 from Mangesh-Khairnar/leave-management
feat: Carry Forward Leave Expiry
2019-08-16 15:39:24 +05:30
deepeshgarg007
8d0b6f9a60 fix: Handling if no default dimension exists 2019-08-16 14:00:26 +05:30
deepeshgarg007
42d9298318 fix: Remove extra space 2019-08-16 13:06:47 +05:30
Mangesh-Khairnar
4b71d9ca9f Merge branch 'develop' into fix-payment-order 2019-08-16 12:42:26 +05:30
deepeshgarg007
2fb6bc9867 fix: Default dimensions in child doctypes 2019-08-15 17:06:32 +05:30
Deepesh Garg
98a6e5cb6c Merge pull request #18742 from rohitwaghchaure/incorrect_sequnce_between_name_and_contact
fix: sequence of customer name and contact in the AR report
2019-08-14 23:20:55 +05:30
Rohit Waghchaure
c5d41af10f fix: sequence of customer name and contact in the AR report 2019-08-14 18:49:19 +05:30
Mangesh-Khairnar
dbb44c8950 fix: restrict the payment order to non received type payment entries 2019-08-14 17:38:44 +05:30
Mangesh-Khairnar
ae2a0fb4c7 chore: remove unused code 2019-08-14 16:50:31 +05:30
Mangesh-Khairnar
2e1d7cbdc2 Merge pull request #18736 from Mangesh-Khairnar/fix-travis
fix: travis
2019-08-14 16:33:34 +05:30
Mangesh-Khairnar
ca55af836b fix: travis 2019-08-14 16:32:02 +05:30
Mangesh-Khairnar
3a42631e65 Merge branch 'develop' of https://github.com/frappe/erpnext into leave-management 2019-08-14 16:11:08 +05:30
Mangesh-Khairnar
107b2768fd refactor: replace raw sql with orm 2019-08-14 16:09:16 +05:30
Anurag Mishra
78690d7b8b fix:payment ammount validation for pos invoices (#18663) 2019-08-14 15:03:58 +05:30
Mangesh-Khairnar
760b078dc9 fix(payroll-entry): commit submitted salary slip check on change (#18694)
* fix(payroll-entry): commit submitted salary slip check onchange

* fix: track submitted ss via flags
2019-08-14 14:40:00 +05:30
Deepesh Garg
2c5dcbe819 fix: Error handling in payment entry (#18720) 2019-08-14 14:26:37 +05:30
Mangesh-Khairnar
06ce1f5a40 fix(quality-inspection): fetch all items for inspection type in process (#18716)
* fix(quality-inspection): fetch all items for inspection type in process

* fix(quality-inspection): add server side validation for in process items
2019-08-14 14:25:59 +05:30
Vignesh S
4454630549 feat(Auto Attendance): Add grace period
Co-authored-by: Karthikeyan S <skarthikeyan1410@gmail.com>
2019-08-14 13:40:02 +05:30
Deepesh Garg
612b0ff9cd Merge pull request #18719 from Anurag810/company_fix_v13
fix: removed hard coded string
2019-08-14 12:12:26 +05:30
Deepesh Garg
5a7f26f4d2 Merge pull request #18717 from Anurag810/company_fix
fix: removed hard coded string form company
2019-08-14 12:11:33 +05:30
Anurag Mishra
de20b083a0 Merge branch 'develop' into company_fix_v13 2019-08-14 12:10:02 +05:30
Anurag Mishra
904cbef4dc fix: removed hard coded string 2019-08-13 19:48:42 +05:30
Anurag Mishra
81d5265385 fix: removed hard coded string 2019-08-13 19:40:36 +05:30
Sagar Vora
33d00bfeae fix: v12 patches sequence (#18610) 2019-08-13 19:39:48 +05:30
rohitwaghchaure
4d7a0aaee1 Merge pull request #18705 from surajshetty3416/fix-payment-entry-account-permission
fix: Check if account passed is accessible under Payment Entry
2019-08-13 18:37:17 +05:30
Sagar Vora
e14a00b887 fix: v12 patches sequence (#18609) 2019-08-13 17:26:56 +05:30
Sahil Khan
77ec5cc8d5 Merge branch 'version-12-hotfix' into version-12 2019-08-13 14:44:18 +05:30
Sahil Khan
f841afd48d bumped to version 12.0.6 2019-08-13 15:04:18 +05:50
Mangesh-Khairnar
dab5b1f319 Merge branch 'develop' of https://github.com/frappe/erpnext into leave-management 2019-08-13 13:33:26 +05:30
Mangesh-Khairnar
8b9b77959d fix: fetch min date when carry forward expiry is greater than leave allocation expiry 2019-08-13 13:20:10 +05:30
Anurag Mishra
d2f13fe0a6 fix: query (#18709) 2019-08-13 13:19:24 +05:30
Suraj Shetty
469e430d38 Merge branch 'fix-payment-entry-account-permission' of github.com:surajshetty3416/erpnext into fix-payment-entry-account-permission 2019-08-13 12:12:03 +05:30
Suraj Shetty
64a962ce97 fix: Re-organise code 2019-08-13 12:10:00 +05:30
Suraj Shetty
1a2600c9ea fix: Ignore account permission check 2019-08-13 12:09:06 +05:30
Himanshu
7df50b6ec7 fix(QMS): TreeView fixes (#18595)
* fix: TreeView fixes

* test: test case fix
2019-08-13 11:58:42 +05:30
Suraj Shetty
80afee04a4 fix: Set address while creating Opportunity from Lead (#18702)
* fix: Set address while creating Opportunity from Lead

* fix: Setting of address in opportunity from Lead
2019-08-13 10:58:29 +05:30
Suraj Shetty
bd2e7c0e53 fix: Comment description 2019-08-12 16:32:14 +05:30
Suraj Shetty
3234f0d299 fix: Check if account passed is accessible under Payment Entry 2019-08-12 16:18:36 +05:30
Faris Ansari
9002a6c195 Merge pull request #18353 from surajshetty3416/refactor-call-popup
fix(call popup): Multiple changes
2019-08-12 15:56:59 +05:30
Faris Ansari
9eda500dbc Merge branch 'develop' into refactor-call-popup 2019-08-12 15:56:35 +05:30
Rohit Waghchaure
fb23773935 fix: reconciled entry has not clearance date set 2019-08-12 15:35:19 +05:30
Frappe PR Bot
037caf096b feat: Updated translation (#18597) 2019-08-12 15:32:37 +05:30
Suraj Shetty
f81b6c7cfb fix: Set address while creating Opportunity from Lead (#18700) 2019-08-12 13:05:22 +05:30
DeeMysterio
1c728a7cf8 fix(delivery note): change the text invoice to sales invoice on make button (#18658) 2019-08-12 13:04:25 +05:30
DeeMysterio
cfce53103b fix(delivery note): change the text invoice to sales invoice on make button (#18666) 2019-08-12 13:03:55 +05:30
Michelle Alva
af969d664f Added Employee Tax Exxxemption category and sub-category links (#18659) 2019-08-12 12:59:59 +05:30
Himanshu
19199baf29 fix: miscellaneous fixes (#18615) 2019-08-12 12:17:28 +05:30
Himanshu
ebf38c5ebd fix: misc fixes (#18616) 2019-08-12 12:17:16 +05:30
Andrew McLeod
c97ffaeac3 fix: Python3 urllib use in item_group.py (now uses six.moves) (#18643) 2019-08-12 12:06:30 +05:30
Andrew McLeod
677c522f01 fix: Python3 urllib use in item_group.py (now uses six.moves) (#18642) 2019-08-12 12:06:23 +05:30
Shivam Mishra
8363a6e585 refactor: remove from date and to date from standard filter (#18696) 2019-08-12 11:58:14 +05:30
Suraj Shetty
83705af0b3 fix: Filters for portal quotation list (#18689)
* fix: Filters for portal quotation list

* fix: Remove unwanted import
2019-08-12 11:51:27 +05:30
sahil28297
214815eb62 fix(patch): force reload child docs (#18672) 2019-08-12 11:49:21 +05:30
sahil28297
a0b9b3cef5 fix(patch): force reload child docs (#18671) 2019-08-12 11:49:14 +05:30
Deepesh Garg
cd7a89da32 Merge pull request #18691 from deepeshgarg007/gstr-3b-non-gst-v12
fix: GSTR 3B report fixes
2019-08-11 19:50:44 +05:30
Deepesh Garg
43dc351209 Merge pull request #18692 from deepeshgarg007/gstr-3b-non-gst-develop
fix: GSTR 3B report fixes
2019-08-11 19:50:29 +05:30
deepeshgarg007
9db6471fae fix: Test Case 2019-08-11 17:29:09 +05:30
deepeshgarg007
4b28b3216b fix: Test Cases 2019-08-11 17:28:55 +05:30
deepeshgarg007
db01743885 fix: Test Case 2019-08-11 15:47:35 +05:30
deepeshgarg007
b87c5d83de fix: Test Cases 2019-08-11 12:40:45 +05:30
Deepesh Garg
8f706399a0 Merge pull request #18631 from netchampfaris/fix-item-template-description
fix(Item Template): Fallback description string
2019-08-11 11:20:46 +05:30
deepeshgarg007
c36abbcda3 fix: GSTR 3B report fixes 2019-08-11 11:01:02 +05:30
deepeshgarg007
dabcb6b531 fix: GSTR 3B report fixes 2019-08-11 10:58:16 +05:30
Deepesh Garg
1fb21cd9e5 Merge branch 'version-12-hotfix' into fix-item-template-description 2019-08-10 17:36:00 +05:30
Deepesh Garg
e6b076cb34 Merge pull request #18632 from netchampfaris/fix-item-template-description-fp
fix(Item Template): Fallback description string
2019-08-10 17:33:59 +05:30
Deepesh Garg
7f0f0d0559 Merge pull request #18685 from marination/fixes_v12_hotfix
fix: Fixed error message in Payment Entry for outstanding invoices not found via filters
2019-08-10 17:27:45 +05:30
Deepesh Garg
0b711d1f8a Merge pull request #18684 from marination/fixes_v12
fix: Fixed error message in Payment Entry for outstanding invoices not found via filters
2019-08-10 17:26:54 +05:30
Suraj Shetty
3f7a757e80 Merge branch 'refactor-call-popup' of github.com:surajshetty3416/erpnext into refactor-call-popup 2019-08-09 19:23:09 +05:30
Suraj Shetty
d4edd284e6 fix: Set Contact or Lead for call log
- Set contact or lead to call log on new Contact or Lead creation
2019-08-09 19:23:04 +05:30
Suraj Shetty
b282f1f154 Merge branch 'develop' into refactor-call-popup 2019-08-09 18:18:17 +05:30
marination
9839a9afb0 fix: Fixed error message in Payment Entry for outstanding invoices not found via filters 2019-08-09 15:54:18 +05:30
marination
4583100537 fix: Fixed error message in Payment Entry for outstanding invoices not found via filters 2019-08-09 15:30:05 +05:30
Deepesh Garg
ca5fdeb9bb Merge pull request #18683 from Mangesh-Khairnar/fix-translation-test
test: Translation in search
2019-08-09 15:29:33 +05:30
rohitwaghchaure
5790fabd54 Merge pull request #18669 from rohitwaghchaure/removed_stock_item_condition_for_sub_contract_v12
fix: allow to subcontract service raw materials
2019-08-09 15:18:38 +05:30
rohitwaghchaure
208eb05519 Merge pull request #18682 from Anurag810/get-item-from-product-bundle-v13
fix: get item from product bundle
2019-08-09 15:18:15 +05:30
rohitwaghchaure
ed6fb66a90 Merge pull request #18679 from Anurag810/get-item-from-product-bundle
fix: get item from product bundle
2019-08-09 15:17:42 +05:30
Mangesh-Khairnar
7c7e9ecfcd test: translation fix 2019-08-09 14:48:36 +05:30
Anurag Mishra
8d4b04e719 fix: get item from product bundle 2019-08-09 14:34:09 +05:30
Mangesh-Khairnar
e173d54054 Merge branch 'develop' of https://github.com/frappe/erpnext into leave-management 2019-08-09 13:37:51 +05:30
Anurag Mishra
24c3b4e00f fix: get item from product bundle 2019-08-09 13:27:59 +05:30
Mangesh-Khairnar
261d132f3a fix: calculate earned leaves based on annual allocation 2019-08-09 13:18:52 +05:30
Himanshu
fa3af74179 Merge pull request #18596 from hrwX/qms-fixes
fix(QMS): TreeView fixes
2019-08-09 11:53:23 +05:30
Himanshu
ffddcb0526 Merge branch 'version-12-hotfix' into qms-fixes 2019-08-09 11:06:36 +05:30
Mangesh-Khairnar
3662ed50d2 fix: multiple changes 2019-08-08 19:47:17 +05:30
Suraj Shetty
7e818067eb Merge branch 'develop' into refactor-call-popup 2019-08-08 19:18:03 +05:30
Suraj Shetty
44b906a43d Merge branch 'refactor-call-popup' of github.com:surajshetty3416/erpnext into refactor-call-popup 2019-08-08 19:16:37 +05:30
Suraj Shetty
429bfcfd83 fix: Make requested changes 2019-08-08 19:16:32 +05:30
Rohit Waghchaure
10e8073204 fix: allow to subcotract service raw materials 2019-08-08 18:20:53 +05:30
Faris Ansari
99c26e068d fix: Set Maintenance Status fields as Read Only (#18634)
Maintenance Status is set based on warranty_expiry_date and
amc_expiry_date. Even if they are editable they are set programmatically
server side. Better to make them as read only.
2019-08-08 17:45:49 +05:30
Faris Ansari
cfd19afabf fix: Set Maintenance Status fields as Read Only (#18633)
Maintenance Status is set based on warranty_expiry_date and
amc_expiry_date. Even if they are editable they are set programmatically
server side. Better to make them as read only.
2019-08-08 17:45:44 +05:30
Mangesh-Khairnar
bafc89f439 refactor: convert raw sql to orm 2019-08-08 17:43:23 +05:30
rohitwaghchaure
9f9184c908 fix: not able to transfer raw materials for subcontracted items (#18650) 2019-08-08 17:41:38 +05:30
rohitwaghchaure
536e6bf57c fix: group by condition in the payment reconciliation (#18657) 2019-08-08 17:40:57 +05:30
Mangesh-Khairnar
f3a5188bec fix: accounting period (#18477)
* fix: accounting period

* test: accounting period

* fix: account period creation

* fix: remove status field from accounting period
2019-08-08 17:39:13 +05:30
Mangesh-Khairnar
9bc4232af6 feat: calculate remaining leaves both multiple simultaneous allocation 2019-08-08 17:11:08 +05:30
Mangesh-Khairnar
5cbe6160ca feat: consider carry forwarded leaves on creation of encashment 2019-08-08 17:06:15 +05:30
Deepesh Garg
f2bf76a2ef fix: Show Cr or Dr symbol in chart of accounts based on balance (#18653)
* fix: Show Cr or Dr symbol in chart of accounts based on balance

* fix: Typo fix in comment
2019-08-08 15:52:39 +05:30
Deepesh Garg
f729eed035 fix: Show Cr or Dr symbol in chart of accounts based on balance (#18654)
* fix: Show Cr or Dr symbol in chart of accounts based on balance

* fix: Typo fix in comment
2019-08-08 15:52:25 +05:30
Deepesh Garg
065e8f3650 fix: Changes in print format due to attribute name changes in frappe (#18639) 2019-08-08 15:51:56 +05:30
Deepesh Garg
04b42600b0 fix: Changes in print format due to attribute name changes in frappe (#18641) 2019-08-08 15:51:50 +05:30
Faris Ansari
df7644a96d fix: Show created serial nos as links in message (#18635)
Fixes #18623
2019-08-08 15:50:24 +05:30
Faris Ansari
afae8a0c46 fix: Show created serial nos as links in message (#18636)
Fixes #18623
2019-08-08 15:50:17 +05:30
Aditya Hase
472050bb8a fix(quickbooks): Do not build global search for QuickBooks Migrator (#18628) 2019-08-08 15:48:35 +05:30
Aditya Hase
f19b7b9122 fix(quickbooks): Do not build global search for QuickBooks Migrator (#18627) 2019-08-08 15:48:09 +05:30
Mangesh-Khairnar
e5c733bdb3 fix: Accounting period (#18630)
* fix: accounting period

* test: accounting period

* fix: account period creation

* fix: remove status field from accounting period
2019-08-08 15:44:11 +05:30
Suraj Shetty
c7716f0bbc Merge branch 'develop' into refactor-call-popup 2019-08-08 12:50:20 +05:30
Himanshu Warekar
b9f2a6048b test: fix test cases 2019-08-07 18:21:38 +05:30
Faris Ansari
3623452839 fix(Item Template): Fallback description string
Fixes #18572
2019-08-07 17:21:51 +05:30
Faris Ansari
2260838933 fix(Item Template): Fallback description string
Fixes #18572
2019-08-07 17:21:22 +05:30
Himanshu
1b48448b7b Merge branch 'version-12-hotfix' into qms-fixes 2019-08-07 11:32:06 +00:00
Rushabh Mehta
6276f1adc5 fix(minor): don't update modified timestamp for overdue tasks 2019-08-07 16:32:44 +05:30
Sahil Khan
9335cdd536 Merge branch 'version-12-hotfix' into version-12 2019-08-07 16:21:14 +05:30
Sahil Khan
c5ae3cc120 bumped to version 12.0.5 2019-08-07 16:41:13 +05:50
Suraj Shetty
9f4b270116 fix: prev_doc 2019-08-06 09:03:23 +05:30
Suraj Shetty
4ca82f9308 fix: Show contact and lead name in list view 2019-08-06 05:49:01 +05:30
Suraj Shetty
207f218db3 Merge branch 'refactor-call-popup' of github.com:surajshetty3416/erpnext into refactor-call-popup 2019-08-06 05:44:28 +05:30
Suraj Shetty
89d8a0b6fd fix: reset_employee_emails_cache method 2019-08-06 05:44:13 +05:30
Himanshu
76c6a050af Merge branch 'version-12-hotfix' into qms-fixes 2019-08-05 17:12:44 +00:00
Deepesh Garg
235166ec41 Merge pull request #18592 from deepeshgarg007/utils_fix_v12
fix: Ambigious column in query
2019-08-05 22:38:45 +05:30
Suraj Shetty
f6f849226e Merge branch 'develop' into refactor-call-popup 2019-08-05 21:25:12 +05:30
Suraj Shetty
7d3f1fef1c fix: Employee selection for call popup
- Check if employee with matched number is also
 scheduled to receive popup
2019-08-05 21:21:45 +05:30
Mangesh-Khairnar
314647572c fix: check for old unexpired allocation 2019-08-05 15:35:19 +05:30
Mangesh-Khairnar
6bed870dfa feat: pro-rata leave allocation 2019-08-05 14:47:53 +05:30
Mangesh-Khairnar
f281f00d43 feat: auto leave encashment 2019-08-05 14:47:53 +05:30
Deepesh Garg
e4abaa7cdd Merge pull request #18591 from deepeshgarg007/utils_fix_develop
fix: Ambigious column in query
2019-08-05 14:45:30 +05:30
Himanshu Warekar
e8f95a2adf fix: TreeView fixes 2019-08-05 14:21:09 +05:30
Rucha Mahabal
d9fa83b196 fix: added Company by default in session defaults (#18471) 2019-08-05 14:15:16 +05:30
deepeshgarg007
23f30cdb8b fix: Aambigious column in query 2019-08-05 12:42:35 +05:30
deepeshgarg007
043f70e9a6 fix: Aambigious column in query 2019-08-05 12:40:50 +05:30
Faris Ansari
9983d90e24 chore: Remove package-lock.json (#18565)
Yarn uses yarn.lock
2019-08-05 12:32:41 +05:30
Deepesh Garg
7638301d01 Merge pull request #18527 from netchampfaris/bom-comparison-tool
feat: BOM Comparison Tool
2019-08-05 11:58:46 +05:30
Suraj Shetty
0957480305 fix: Show popup to employees with same phone number 2019-08-05 11:32:51 +05:30
rohitwaghchaure
9601b6c601 feat: added checkbox 'Update Consumed Material Cost In Project' in work order to don't update the consumed material cost in the project for the subassembely work orders (#18533) 2019-08-05 10:40:23 +05:30
Frappe PR Bot
833afea3e3 feat: Updated translation (#18542) 2019-08-05 10:38:37 +05:30
Anurag Mishra
08df6bda41 fix: query (#18468) 2019-08-05 10:27:25 +05:30
Mangesh-Khairnar
31c5c1e562 fix(payroll-entry): show make bank entry button (#18497)
* fix(payroll-entry): show make bank entry button when manually submitting salary slip

* Update payroll_entry.py
2019-08-05 10:21:19 +05:30
Anurag Mishra
7e1987ed48 feat: validate cwip accounts for journal Entry (#18518) 2019-08-05 10:18:57 +05:30
Anurag Mishra
f9fb92ebb0 feat: validate cwip accoutns in journal entry (#18519) 2019-08-05 10:18:37 +05:30
Mangesh-Khairnar
a3d5200194 fix(sales-order): update items (#18535)
* fix(sales-order): update items

* fix: minor changes

* Update: accounts controller
2019-08-05 10:16:40 +05:30
Mangesh-Khairnar
9accae3ddc fix(sales-order): update items (#18536)
* fix(sales-order): update items

* fix: minor changes
2019-08-05 10:16:25 +05:30
Deepesh Garg
8b0302bab9 fix: Make conversion rate optional for non itemized items (#18540) 2019-08-05 10:14:19 +05:30
Deepesh Garg
cfb899451f fix: Make conversion rate optional for non itemized items (#18541) 2019-08-05 10:14:05 +05:30
Deepesh Garg
fb5aa43e77 fix: Type error handling while getting material request items (#18549)
* fix: Type error handling while getting material request items

* fix: Remove flt
2019-08-05 10:12:42 +05:30
Deepesh Garg
713d4bb1ba fix: Type error handling while getting material request items (#18550)
* fix: Type error handling while getting material request items

* fix: Remove flt
2019-08-05 10:12:17 +05:30
Deepesh Garg
a7eaa4de14 fix: Customer price list not honored in shopping cart (#18556) 2019-08-05 10:10:53 +05:30
Deepesh Garg
65ad4287b6 fix: Customer price list not honored in shopping cart (#18557) 2019-08-05 10:10:38 +05:30
Faris Ansari
b4678d3f21 chore: Remove package-lock.json (#18564)
Yarn uses yarn.lock
2019-08-05 10:09:32 +05:30
Suraj Shetty
580fa48642 fix: Quotation list in customer portal (#18579)
* fix: Quotation list in customer portal

Customers were not able to see their quotations because of
 the recent customer field removal from quotation

* fix: Remove duplicate code
2019-08-05 10:08:35 +05:30
Mangesh-Khairnar
06a0afa039 Feat: Notify by email feature (#18587)
* feat: notify user by email in employee onboarding control

* fix: create task on update after submit
2019-08-05 10:07:05 +05:30
Nabin Hait
a813571c17 Update employee_advance.py 2019-08-05 10:05:28 +05:30
Mangesh-Khairnar
d66396f4e9 fix(employee-advance): update employee advance on change in expense claim (#18588)
* fix(employee-advance): update employee advance on rejection/cancellation of expense claim

* fix(expense-claim): display appropriate buttons only if linked transactions are valid

* Update employee_advance.py
2019-08-05 10:04:05 +05:30
Mangesh-Khairnar
c92e823651 fix(employee-advance): update employee advance on change in expense claim (#18590)
* fix(employee-advance): update employee advance on rejection/cancellation of expense claim

* fix(expense-claim): display appropriate buttons only if linked transactions are valid
2019-08-05 10:02:32 +05:30
Suraj Shetty
35c4b78e66 Merge branch 'develop' of github.com:frappe/erpnext into refactor-call-popup 2019-08-04 22:23:03 +05:30
rohitwaghchaure
ed7e074985 Merge pull request #18570 from rohitwaghchaure/add_serial_no_button_not_working
fix: add serial no button not working for the delivery note
2019-08-04 12:02:43 +05:30
Deepesh Garg
6f39db6a18 Merge pull request #18576 from deepeshgarg007/taxes_and_totals_v12
fix: Error handling in taxes and totals
2019-08-03 18:27:14 +05:30
Deepesh Garg
7668cd7053 Merge pull request #18577 from deepeshgarg007/taxes_and_totals_v13
fix: Error handling in taxes and totals
2019-08-03 18:26:54 +05:30
deepeshgarg007
b656529818 fix: Error handling in taxes and totals 2019-08-03 13:49:33 +05:30
deepeshgarg007
8bf19ce81a fix: Error handling in taxes and totals 2019-08-03 13:44:44 +05:30
Deepesh Garg
4d38a5043f Merge pull request #18573 from deepeshgarg007/error_report_develop
fix: Remove payment order from bank dashboard
2019-08-03 12:17:13 +05:30
Deepesh Garg
2f1633bf41 Merge pull request #18574 from deepeshgarg007/error_report_v12
fix: Remove payment order from bank dashboard
2019-08-03 12:16:52 +05:30
deepeshgarg007
a499079250 fix: Remove non standard fieldname 2019-08-03 10:27:33 +05:30
deepeshgarg007
75465775b4 Merge branch 'version-12-hotfix' of https://github.com/frappe/erpnext into error_report_v12 2019-08-03 10:26:46 +05:30
deepeshgarg007
b0916a35dc fix: Remove payment order from bank dashboard 2019-08-03 10:24:04 +05:30
deepeshgarg007
93e46310b8 fix: Remove payment order from bank dashboard 2019-08-03 10:17:44 +05:30
Suraj Shetty
4b3b358ac6 fix: Show correct label instead of showing undefined (#18559) 2019-08-02 21:46:38 +05:30
Suraj Shetty
e45102b913 fix: Show correct label instead of showing undefined (#18560) 2019-08-02 21:46:28 +05:30
Rohit Waghchaure
dadd049cbf fix: add serial no button not working for the delivery note 2019-08-02 19:11:31 +05:30
Sahil Khan
8426ff726a Merge branch 'version-12-hotfix' into version-12 2019-08-02 14:27:59 +05:30
Sahil Khan
968da1cd21 bumped to version 12.0.4 2019-08-02 14:47:59 +05:50
sahil28297
7d7e4534dd Merge pull request #18566 from scmmishra/task-fix
fix: task not updated issue
2019-08-02 14:15:19 +05:30
sahil28297
c7abc025ff Merge branch 'develop' into task-fix 2019-08-02 14:14:52 +05:30
sahil28297
77313685da Merge pull request #18567 from scmmishra/task-fix-patch1
fix: task not updated issue
2019-08-02 14:14:27 +05:30
Shivam Mishra
0313dc0f8c fix: task not updated issue 2019-08-02 14:12:36 +05:30
Shivam Mishra
dc53e8ebb0 fix: task not updated issue 2019-08-02 13:56:49 +05:30
Suraj Shetty
117161401a fix: Check zero valuation rate only for valid doctypes (#18528)
fix: Check zero valuation rate only for valid doctypes
2019-08-02 11:22:47 +05:30
Suraj Shetty
c6857e562b fix: Check zero valuation rate only for valid doctypes (#18529)
fix: Check zero valuation rate only for valid doctypes
2019-08-02 11:22:31 +05:30
Suraj Shetty
3f31a56fb3 feat(customer): Add report to show item prices per Customer (#17930)
feat(customer): Add report to show item prices per Customer
2019-08-02 11:11:51 +05:30
Suraj Shetty
c154e57603 Merge branch 'develop' into stock-ledger-fix_develop 2019-08-02 08:44:21 +05:30
Suraj Shetty
b559bf6de6 Merge branch 'version-12-hotfix' into stock-ledger-fix 2019-08-02 08:43:54 +05:30
Rohan
fc04d334da Merge branch 'develop' into develop-customer-item-price-report 2019-08-01 17:51:03 +05:30
Deepesh Garg
4be00502cc Merge pull request #18546 from deepeshgarg007/bank_dashboard_fix
fix: Payment Order link fix in bank dashboard
2019-08-01 15:24:53 +05:30
Deepesh Garg
097cc13072 Merge pull request #18547 from deepeshgarg007/bank_dashboard_v12
fix: Payment Order link fix in bank dashboard
2019-08-01 15:24:37 +05:30
deepeshgarg007
c16ef32b6b fix: Payment Order link fix in bank dashboard 2019-08-01 14:10:03 +05:30
deepeshgarg007
8704bc6f39 fix: Payment Order link fix in bank dashboard 2019-08-01 13:24:48 +05:30
Suraj Shetty
31e3dada0e Merge branch 'version-12-hotfix' into bom-comparison-tool 2019-08-01 12:59:19 +05:30
Faris Ansari
d9e482759c style: Missing semicolon 2019-08-01 12:53:19 +05:30
Mangesh-Khairnar
8fb600162e fix: remove all leaves via scheduler 2019-07-31 19:31:26 +05:30
Mangesh-Khairnar
5eac8703da fix: create reverse expiry for back dated leaves 2019-07-31 19:28:21 +05:30
Deepesh Garg
d301d26fda fix: Enhancement in credit note (#18511)
* fix: Credit note enhancement

* Fix: Print format for Sales Invoice Return

* fix: Zero quantity validation fix for credit note
2019-07-31 15:58:19 +05:30
Deepesh Garg
b65c761b16 fix: Enhancement in credit note (#18510)
* fix: Credit note enhancement

* Fix: Print format for Sales Invoice Return

* fix: Zero quantity validation fix for credit note
2019-07-31 15:58:01 +05:30
rohitwaghchaure
fdb5e6b2e5 Merge pull request #18531 from rohitwaghchaure/scrap_item_not_adding_v12_hotfix
fix: scrap item not adding while doing finished good entry
2019-07-31 00:47:01 +05:30
Rohit Waghchaure
862363521b fix: scrap item not adding while doing finished good entry 2019-07-30 23:57:58 +05:30
deepeshgarg007
f9c0ef3eb3 fix: Check zero valuation rate only for valid doctypes 2019-07-30 19:01:53 +05:30
deepeshgarg007
548293ad08 fix: Check zero valuation rate only for valid doctypes 2019-07-30 18:49:19 +05:30
Faris Ansari
f650cb8e4b style: Indent and missing semicolon 2019-07-30 17:46:15 +05:30
Faris Ansari
a4ada93834 fix: Field labels 2019-07-30 17:11:34 +05:30
Faris Ansari
66064c7827 fix: Add BOM Comparison Tool to module view 2019-07-30 17:11:18 +05:30
Faris Ansari
1214e2d2a4 feat: BOM Comparison Tool 2019-07-30 16:54:46 +05:30
Suraj Shetty
36c724d7b5 fix(Issue): reset issue fulfilled indicator (#18454)
fix(Issue): reset issue fulfilled indicator
2019-07-29 18:27:53 +05:30
Deepesh Garg
62daa43a4c Merge pull request #18513 from deepeshgarg007/trail_balance_fix
fix: Opening balance not getting calculated in trail balance report
2019-07-29 15:38:39 +05:30
rohitwaghchaure
a55e4e2c03 Merge pull request #18452 from rohitwaghchaure/scrap_item_not_adding
fix: scrap item not adding while doing finished good entry
2019-07-29 15:30:24 +05:30
deepeshgarg007
4c652396d0 fix: Opening balance not getting calculated in trail_balance_report 2019-07-29 14:57:33 +05:30
Deepesh Garg
8d00ee4f81 Merge pull request #18403 from deepeshgarg007/defaut_accounting_dimension
fix: Company specific default accounting dimensions
2019-07-29 12:12:40 +05:30
deepeshgarg007
ec421df4e4 fix: Code cleanup 2019-07-29 08:58:56 +05:30
deepeshgarg007
6a43f6718a fix: Reload doc in patch 2019-07-28 10:30:32 +05:30
deepeshgarg007
a65ad10c62 fix: GL entry validation fix 2019-07-27 19:06:36 +05:30
deepeshgarg007
4fce891241 Merge branch 'develop' of https://github.com/frappe/erpnext into defaut_accounting_dimension 2019-07-27 18:11:34 +05:30
Himanshu
9443e8e85f Merge branch 'develop' into iss_ful_fix_v13 2019-07-26 12:51:09 +00:00
Deepesh Garg
ce63b1f669 Merge pull request #18317 from deepeshgarg007/stock_ageing_fix
fix: Enhancement in stock ageing report
2019-07-26 15:45:48 +05:30
Saurabh
af19347376 Merge branch 'develop' into scrap_item_not_adding 2019-07-26 15:10:17 +05:30
Himanshu
e87fbcc286 Merge branch 'develop' into iss_ful_fix_v13 2019-07-26 07:31:44 +00:00
Deepesh Garg
941e21092c Merge branch 'develop' into stock_ageing_fix 2019-07-26 12:47:05 +05:30
Deepesh Garg
c391d65dc3 Merge pull request #18488 from adityahase/faster
perf(travis): Faster builds
2019-07-26 12:41:01 +05:30
Himanshu
0162d0a2ea Merge branch 'develop' into iss_ful_fix_v13 2019-07-26 06:58:55 +00:00
rohitwaghchaure
ad89b029c2 Merge pull request #18494 from netchampfaris/parent-account-child-account-currency-fp
fix(Account): Pass parent currency to child currency
2019-07-26 12:13:27 +05:30
deepeshgarg007
15154d4bd0 Merge branch 'develop' of https://github.com/frappe/erpnext into stock_ageing_fix 2019-07-26 10:30:22 +05:30
deepeshgarg007
dda6b5cd53 fix: Add accounting dimensions to subscription and opening invoice tool 2019-07-26 09:14:42 +05:30
deepeshgarg007
aa92df4ddc fix: Test cases for accounting dimensions 2019-07-26 09:14:42 +05:30
deepeshgarg007
0ce2afc9bc fix: Test Cases for accounting dimensions 2019-07-26 09:14:42 +05:30
deepeshgarg007
41a1cd954c fix: Set query fix 2019-07-26 09:14:42 +05:30
deepeshgarg007
b124aff0bb fix: Codacy Fixes 2019-07-26 09:14:42 +05:30
deepeshgarg007
703fc08467 fix: Patch 2019-07-26 09:14:42 +05:30
deepeshgarg007
6dd5f74671 fix: Remove dimensions from boot 2019-07-26 09:14:42 +05:30
deepeshgarg007
ada1ac8013 fix: Added default dimension and filter in bootinfo 2019-07-26 09:14:42 +05:30
deepeshgarg007
efd6307bc4 fix: Default Accounting Dimensions in doctypes 2019-07-26 09:14:42 +05:30
Faris Ansari
0ec747f57c fix(Account): Pass parent currency to child currency
In a scenario where Parent Company Account's Currency is different
from it's default currency, the Account Currency of Child would be set
from the default currency of Company which might be wrong
2019-07-26 08:55:34 +05:30
Deepesh Garg
059c568f08 Merge pull request #18482 from deepeshgarg007/bank_reco_develop
fix: Dynamic link issue fix in Bank reconciliation statement
2019-07-26 08:48:49 +05:30
Deepesh Garg
641e8fea02 Merge branch 'develop' into bank_reco_develop 2019-07-26 08:48:02 +05:30
Deepesh Garg
cd645de4f9 Merge pull request #18474 from deepeshgarg007/gstr-1-query-v13
fix: GSTR-1 query fix
2019-07-26 08:45:59 +05:30
Aditya Hase
96eec7e2cd chore: Empty commit 2019-07-26 05:06:42 +05:30
Aditya Hase
1e82638ae8 perf(tests): Remove unnecessary tearDown code 2019-07-26 03:52:17 +05:30
Aditya Hase
ff79463e36 perf(travis): Use setUpClass instead of setUp 2019-07-26 03:08:45 +05:30
Aditya Hase
6bc2988623 fix(stock): Fix travis builds 2019-07-26 03:08:45 +05:30
Aditya Hase
0f072d7d40 perf(tests): Use country with lesser fixtures 2019-07-26 01:11:57 +05:30
Aditya Hase
0ae786adec perf(tests): Do not unnecessarily create companies 2019-07-26 00:38:51 +05:30
Aditya Hase
6a5b7f751c perf(tests): Use setUpClass and tearDownClass instead of setUp and tearDown 2019-07-26 00:23:13 +05:30
Aditya Hase
a630a24040 fix(travis): Install wkhtmltopdf 2019-07-25 22:40:26 +05:30
Aditya Hase
fdb395c977 fix(travis): Remove redundant arguments 2019-07-25 21:51:12 +05:30
Aditya Hase
dac46ed6e1 fix(travis): Use --site parameter 2019-07-25 21:50:46 +05:30
Aditya Hase
6002fd7820 fix(travis): Merge install and before_script 2019-07-25 21:39:30 +05:30
Aditya Hase
4b680178be fix(travis): Execute get-app before bench start 2019-07-25 21:38:07 +05:30
Aditya Hase
5542af9be8 perf(travis): Use travis optimizations 2019-07-25 21:36:22 +05:30
Aditya Hase
1de45877b1 perf(travis): Do not build assets 2019-07-25 21:28:15 +05:30
Aditya Hase
13919440a7 perf(travis): Remove unnecessary processes from Procfile 2019-07-25 21:27:20 +05:30
Aditya Hase
543996652b fix(travis): Use MariaDB credentials from site_config.json 2019-07-25 21:25:53 +05:30
Aditya Hase
6f978d2497 perf(travis): Do not waste 10 seconds 2019-07-25 21:23:43 +05:30
Aditya Hase
97ad6352af fix(travis): Execute get-app and reinstall after bench start 2019-07-25 21:23:24 +05:30
Aditya Hase
6e2fa02c56 fix(travis): Do not disable scheduler using CLI 2019-07-25 21:21:54 +05:30
Aditya Hase
6f025e7b4f fix(travis): Do not use use_site 2019-07-25 21:21:19 +05:30
Aditya Hase
8005022508 fix(travis): Setup MariaDB 2019-07-25 21:19:30 +05:30
Aditya Hase
06ca6f7705 fix(travis): Remove unused files 2019-07-25 21:18:05 +05:30
Aditya Hase
ac185989ff fix(travis): Remove travis/bench_init.sh 2019-07-25 21:15:30 +05:30
Aditya Hase
306a410b77 fix(travis): Do not execute install.py 2019-07-25 21:11:18 +05:30
Aditya Hase
c49d45497c fix(travis): Install coverall after tests 2019-07-25 21:10:06 +05:30
Aditya Hase
828699f26a fix(travis): Use MariaDB 10.3 2019-07-25 21:09:34 +05:30
Aditya Hase
7204bcb932 fix(travis): Restructure build matrix 2019-07-25 17:48:17 +05:30
Mangesh-Khairnar
5ca3e83a00 feat: add ledger link in hr config 2019-07-25 14:35:39 +05:30
deepeshgarg007
f56284b0c1 Dynamic link issue fix in Bank reconciliation statement 2019-07-25 12:47:32 +05:30
deepeshgarg007
b1f3e0224c fix: GSTR-1 query fix 2019-07-24 21:36:56 +05:30
Anurag Mishra
0505135f90 Merge branch 'develop' into refactor-call-popup 2019-07-24 18:33:14 +05:30
Aditya Hase
14600e6a3e fix(travis): Remove unnecessary commands 2019-07-24 15:18:52 +05:30
Himanshu Warekar
20e1a3e199 fix: reset issue fulfilled indicator 2019-07-23 17:00:23 +05:30
Rohit Waghchaure
bcfbc792b4 fix: scrap item not adding while doing finished good entry 2019-07-23 16:05:29 +05:30
Mangesh-Khairnar
bb1be2e2ee chore: change fieldname for carried forward leave expiry 2019-07-23 13:18:40 +05:30
Aditya Hase
48d9cfe304 fix(website): Remove references to Product Settings.products_a… (#18448)
This was accidentally added back in 34c551d9a5 (diff-f0a387cdb305471e74e523ecc4e646ac)
2019-07-23 12:05:31 +05:30
deepeshgarg007
a19d1d6926 fix: Fixes in stock ageing report 2019-07-23 08:30:09 +05:30
Mangesh-Khairnar
fd1d4c2927 chore: split expire allocation 2019-07-22 18:05:27 +05:30
Mangesh-Khairnar
08c02287dd fix: expiry allocaton after creating all the transactions 2019-07-22 17:47:03 +05:30
deepeshgarg007
d9cc84892b fix: Use get_serial_no and remove batch 2019-07-22 15:56:41 +05:30
Mangesh-Khairnar
f6cf58fa8c fix: change get all to sql list 2019-07-22 15:32:41 +05:30
deepeshgarg007
3877b5407c fix: Remove batch 2019-07-22 15:30:18 +05:30
Suraj Shetty
1e9a6e6b7e Merge branch 'refactor-call-popup' of github.com:surajshetty3416/erpnext into refactor-call-popup 2019-07-22 15:19:31 +05:30
Suraj Shetty
4177ecb6b2 Merge branch 'develop' of github.com:frappe/erpnext into refactor-call-popup 2019-07-22 15:18:03 +05:30
Mangesh-Khairnar
cbc22e6369 Merge branch 'develop' of https://github.com/frappe/erpnext into leave-management 2019-07-22 13:57:04 +05:30
Mangesh-Khairnar
29e9f14f95 fix: minor changes 2019-07-22 13:54:39 +05:30
deepeshgarg007
55ca054cb4 Merge branch 'develop' of https://github.com/frappe/erpnext into stock_ageing_fix 2019-07-22 12:46:30 +05:30
Suraj Shetty
4bf65dd0d9 Merge branch 'develop' into develop-customer-item-price-report 2019-07-18 10:15:20 +05:30
Suraj Shetty
06b56adc71 Merge branch 'develop' into develop-customer-item-price-report 2019-07-17 17:29:42 +05:30
Suraj Shetty
9eb091162a Merge branch 'develop' into refactor-call-popup 2019-07-17 13:23:54 +05:30
Suraj Shetty
82cfccab2f fix: Get user_id of employee instead of name
- Remove caching from lead
2019-07-17 11:52:35 +05:30
deepeshgarg007
4a7f370b15 fix: Consider serial no and batches in Stock ageing report 2019-07-16 16:08:50 +05:30
Suraj Shetty
2295921821 Merge branch 'develop' into refactor-call-popup 2019-07-16 13:54:07 +05:30
Suraj Shetty
e7d277d51e fix: Last communication query 2019-07-16 13:53:41 +05:30
Suraj Shetty
f5dd494716 fix(call popup): Multiple changes
- Remove summary from call log use comments instead
- Add contact and lead if matched to the call log
- Add received by field with Employee Link
- DocPerm to allow Employees to have read access on call log
- Remove unwanted code
- Add a method to get lead just by providing phone number
- Show popup only to Employee to which the system is trying to connect.
- Remove custom code from call popup dialog and replace it with
 standard fields
- Increase timeout to hide popup after the call has been disconnected.
2019-07-16 11:07:25 +05:30
deepeshgarg007
6ecbd97fbe Merge branch 'develop' of https://github.com/frappe/erpnext into stock_ageing_fix 2019-07-16 09:02:48 +05:30
deepeshgarg007
581d931031 fix: Enhancement in stock ageing report 2019-07-13 21:58:32 +05:30
deepeshgarg007
d43d19acfd Merge branch 'develop' of https://github.com/frappe/erpnext into stock_ageing_fix 2019-07-13 11:40:05 +05:30
deepeshgarg007
1dc124cf75 fix: Changed columns to new dict form 2019-07-10 14:49:25 +05:30
Mangesh-Khairnar
80fb0bf520 fix: add employee leave balance report to the dashboard 2019-07-03 13:06:46 +05:30
Mangesh-Khairnar
8d19faa598 Merge branch 'develop' of https://github.com/frappe/erpnext into leave-management 2019-07-03 11:44:00 +05:30
Mangesh-Khairnar
0d4db95d99 fix: skip expired leaves on allocation end date 2019-07-03 11:35:51 +05:30
Mangesh-Khairnar
bd999b0908 fix: minor changes 2019-06-21 00:49:48 +05:30
Mangesh-Khairnar
439313e524 Merge branch 'develop' of https://github.com/frappe/erpnext into leave-management 2019-06-19 13:10:53 +05:30
Mangesh-Khairnar
12a2b21465 fix: track status for pending leaves 2019-06-19 13:00:52 +05:30
Rohan Bansal
0426636a32 fix(customer): Improve performance by reducing queries 2019-06-17 12:16:12 +05:30
Mangesh-Khairnar
1db0fc91a5 fix: check generated ledger entries to avoid overlap 2019-06-14 15:48:31 +05:30
Mangesh-Khairnar
fefdac432c refactor: add json changes 2019-06-14 15:39:50 +05:30
Mangesh-Khairnar
24fbe23c8d fix: add indicators for expire in the leave allocation list 2019-06-14 15:36:26 +05:30
Mangesh-Khairnar
87adaed933 fix: generate ledger entries for all leave transactions 2019-06-14 15:13:53 +05:30
Mangesh-Khairnar
4e1b60d401 style: change formatting 2019-06-14 11:15:54 +05:30
Rohan Bansal
e3a3306b30 feat(customer): Add report to show item prices per Customer 2019-06-13 14:53:33 +05:30
Mangesh-Khairnar
283d2c5ca2 fix: only create leave application ledger on intermediate allocation expiry 2019-06-11 16:35:29 +05:30
Mangesh-Khairnar
b16d395a8f Merge branch 'develop' of https://github.com/frappe/erpnext into leave-management 2019-06-07 11:33:06 +05:30
Mangesh-Khairnar
e4d03bf0d0 fix: consider expiry in leaves 2019-06-07 11:30:27 +05:30
Mangesh-Khairnar
368a974368 style: change formatting 2019-06-06 20:43:32 +05:30
Mangesh-Khairnar
86e0f4c617 Merge branch 'develop' of https://github.com/frappe/erpnext into leave-management 2019-06-06 20:41:28 +05:30
Mangesh-Khairnar
c7b9ae9c5e fix: get leave balance based on the ledger entries 2019-06-06 20:38:59 +05:30
Mangesh-Khairnar
b840ba4407 feat: show dashboard on submit 2019-06-06 20:38:23 +05:30
Mangesh-Khairnar
00c607116b fix: handle negative leaves without allocation 2019-06-06 20:37:34 +05:30
Mangesh-Khairnar
f13243a92e feat: fetch annual allocation based on leave policy 2019-06-06 20:35:10 +05:30
Mangesh-Khairnar
3863fc5fb2 feat: create ledger entry for each earned leave 2019-06-06 20:34:10 +05:30
Mangesh-Khairnar
050f65beb4 feat: fetch leave allocation from ledger entry 2019-06-06 20:32:01 +05:30
Mangesh-Khairnar
351f4d53a0 feat: add employee filter to employee leave balance 2019-06-05 21:22:07 +05:30
Mangesh-Khairnar
62011c9dc4 feat: add link to policy 2019-06-05 21:16:27 +05:30
Mangesh-Khairnar
afa1dc4ffa feat: expire current allocation 2019-06-03 20:09:22 +05:30
Mangesh-Khairnar
c5385e141b fix: add mandatory reason fields in leave application 2019-05-31 00:53:28 +05:30
Mangesh-Khairnar
95e5d812fe fix: UX changes 2019-05-30 22:22:15 +05:30
Mangesh-Khairnar
91e62f575e fix: department filters in employee leave balance 2019-05-30 13:38:35 +05:30
Mangesh-Khairnar
2124d9884b fix: pass positional arguments on creation of leave application 2019-05-30 13:21:34 +05:30
Mangesh-Khairnar
aafb5cb6f6 fix: ignore expired non-carry forwarded allocation on calculating leaves taken 2019-05-30 13:13:14 +05:30
Mangesh-Khairnar
ded33a7e2e fix: ledger entries creation 2019-05-29 19:12:19 +05:30
Mangesh-Khairnar
7cc6a67c18 add department filter to employee leave balance 2019-05-29 15:59:20 +05:30
Mangesh-Khairnar
8f47bffa0e fix: create an instance 2019-05-29 15:54:14 +05:30
Mangesh-Khairnar
ae4228aed4 fix: fetch queries 2019-05-28 13:24:15 +05:30
Mangesh-Khairnar
9d6151d200 patch: add reload doc for leave ledger entry 2019-05-28 11:45:51 +05:30
Mangesh-Khairnar
c6d6adcbf3 fix: ledger entry creation for encashment 2019-05-28 09:44:38 +05:30
Mangesh-Khairnar
61bb236cfa test: delete ledger entry after each test to maintain balance 2019-05-27 19:36:40 +05:30
Mangesh-Khairnar
35786c0067 Merge branch 'leave-management' of https://github.com/Mangesh-Khairnar/erpnext into leave-management 2019-05-27 15:42:11 +05:30
Mangesh-Khairnar
6f69bbe1d7 test: leave ledger balance 2019-05-27 15:40:36 +05:30
Mangesh-Khairnar
7fbaef5de3 fix: expiry ledger creation 2019-05-27 15:39:48 +05:30
Nabin Hait
c9a4111f4d Merge branch 'develop' into leave-management 2019-05-27 13:59:27 +05:30
Mangesh-Khairnar
705b7dc9c2 Merge branch 'develop' of https://github.com/frappe/erpnext into leave-management 2019-05-27 11:21:06 +05:30
Mangesh-Khairnar
71cdcb3593 style: add a more descriptive method name 2019-05-27 11:00:04 +05:30
Mangesh-Khairnar
24248f687b patch: create entries for only missing transactions 2019-05-27 10:58:42 +05:30
Mangesh-Khairnar
45197965d7 fix: give cancellation permission to hr manager 2019-05-27 03:26:48 +05:30
Mangesh-Khairnar
d751281fa7 fix: application and leave encashment test cases 2019-05-27 03:23:58 +05:30
Mangesh-Khairnar
e7d307e6bf fix: only expire carry forwarded allocation via scheduler 2019-05-27 03:21:32 +05:30
Mangesh-Khairnar
2417c93d0e feat: create expiry and carry forward calculation on leave allocation creation 2019-05-27 03:20:10 +05:30
Mangesh-Khairnar
c99f644ffe test: carry forward and expiry allocation 2019-05-27 03:13:47 +05:30
Mangesh-Khairnar
7a7f4bd822 fix: leave balance calculation 2019-05-27 03:12:57 +05:30
Mangesh-Khairnar
9e3b688333 test: create leave policy 2019-05-27 03:10:39 +05:30
Mangesh-Khairnar
f3926d0fcb patch: leave ledger entries 2019-05-26 20:17:16 +05:30
Mangesh-Khairnar
45cf02308e fix: prevent manual creation of ledger entries 2019-05-23 20:29:18 +05:30
Mangesh-Khairnar
8ef81870bb test: create leave ledger entry for encashment 2019-05-23 20:28:16 +05:30
Mangesh-Khairnar
5ba17c87e5 feat: remove update allocation after submit 2019-05-22 18:29:22 +05:30
Mangesh-Khairnar
afb0c4aa43 fix: delete entry on cancellation of transaction 2019-05-15 21:50:34 +05:30
Mangesh-Khairnar
f8f02c508d feat: create leave ledger entry on leave encashment creation 2019-05-15 21:50:06 +05:30
Mangesh-Khairnar
50037f8609 fix: consider min days remaining as remaining leaves 2019-05-15 21:49:27 +05:30
Mangesh-Khairnar
6ba9a128e7 feat: calculate leave balance using ledger entries 2019-05-15 15:53:54 +05:30
Mangesh-Khairnar
964deaca96 test: creation of ledger entries on application submit 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
201aeeb20d test: creation of ledger entries on allocation submit 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
170b8dded8 fix: expiry logic for carry forwarded allocation 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
9bb4b8e8b2 feat: create expiry ledger entry on allocation period completion 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
cf8f4bda8f fix: skip application fetch for non allocation records 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
783bd89413 feat: handle cancellation workflow for leave application 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
5448edff2c feat: delete cancelled allocation from ledger 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
01490f1560 feat: add cancellation workflow for leave allocation ledger entry 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
5ad83c06c2 feat: add ledger entries on leave addition 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
679371e397 feat: get carry forwarded leaves via ledger entries 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
2b421c39b5 feat: add transaction details in ledger 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
1de990b2ac feat: create leave ledger entry 2019-05-15 15:53:53 +05:30
Mangesh-Khairnar
5e2b067107 feat: display carry forwarded allocation days and total leaves allocated 2019-05-15 15:53:52 +05:30
Mangesh-Khairnar
0abf5d340c fix: carry forwarded allocation period validation 2019-05-15 15:53:52 +05:30
Mangesh-Khairnar
d01863707c test: pass leave type as params 2019-05-15 15:53:52 +05:30
Mangesh-Khairnar
70cf4a6796 feat: validate leave allocation period to be within expiry limits 2019-05-15 15:53:52 +05:30
Mangesh-Khairnar
99c9cfaaed feat: generate leave allocation for carry forwarded leaves 2019-05-15 15:53:52 +05:30
Mangesh-Khairnar
4badca54af feat: validate leave expiry days 2019-05-15 15:53:52 +05:30
Mangesh-Khairnar
0c0bfb1ef0 feat: calculate carry forward leaves allocation 2019-05-15 15:53:52 +05:30
Mangesh-Khairnar
d6c5b6320f feat: add a field for conditionally displaying carry forwarded leave 2019-05-15 15:53:52 +05:30
Mangesh-Khairnar
e46d3a87ea feat: set carry forwarded leave allocation 2019-05-15 15:53:52 +05:30
Mangesh-Khairnar
c28d2e4b2a test: create leave allocation check 2019-05-15 15:53:51 +05:30
Mangesh-Khairnar
bd3b3ea12c test: create leave type 2019-05-15 15:53:51 +05:30
Mangesh-Khairnar
7c6b6eae5b feat: set leave allocation on carry forward check 2019-05-15 15:53:51 +05:30
Mangesh-Khairnar
1208ca6a36 feat: add old leaves to track carry forward leave allocation 2019-05-15 15:53:51 +05:30
Mangesh-Khairnar
c182a5687e feat: add validation for carry forward leave expiry 2019-05-15 15:53:51 +05:30
Mangesh-Khairnar
fd53c64d5d feat: create carried forward leave expiry option 2019-05-15 15:53:51 +05:30
218 changed files with 487767 additions and 469438 deletions

View File

@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = '12.0.3'
__version__ = '12.0.8'
def get_default_company(user=None):
'''Get default company for user'''

View File

@@ -121,7 +121,11 @@ frappe.treeview_settings["Account"] = {
},
onrender: function(node) {
if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){
var dr_or_cr = in_list(["Liability", "Income", "Equity"], node.data.root_type) ? "Cr" : "Dr";
// 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 text-muted small">'
+ (node.data.balance_in_account_currency ?

View File

@@ -17,8 +17,7 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
"options": "Company"
},
{
"fieldname": "reference_document",
@@ -34,8 +33,7 @@
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Default Dimension",
"options": "reference_document",
"reqd": 1
"options": "reference_document"
},
{
"columns": 3,
@@ -55,7 +53,7 @@
}
],
"istable": 1,
"modified": "2019-07-17 23:34:33.026883",
"modified": "2019-08-15 11:59:09.389891",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Dimension Detail",

View File

@@ -167,39 +167,7 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "Open\nClosed",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -273,7 +241,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-04-13 19:14:47.593753",
"modified": "2019-08-01 19:14:47.593753",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Period",

View File

@@ -7,6 +7,8 @@ import frappe
from frappe.model.document import Document
from frappe import _
class OverlapError(frappe.ValidationError): pass
class AccountingPeriod(Document):
def validate(self):
self.validate_overlap()
@@ -34,12 +36,13 @@ class AccountingPeriod(Document):
}, as_dict=True)
if len(existing_accounting_period) > 0:
frappe.throw(_("Accounting Period overlaps with {0}".format(existing_accounting_period[0].get("name"))))
frappe.throw(_("Accounting Period overlaps with {0}")
.format(existing_accounting_period[0].get("name")), OverlapError)
def get_doctypes_for_closing(self):
docs_for_closing = []
#if not self.closed_documents or len(self.closed_documents) == 0:
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", "Bank Reconciliation", "Asset", "Purchase Order", "Sales Order", "Leave Application", "Leave Allocation", "Stock Entry"]
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", "Bank Reconciliation",
"Asset", "Purchase Order", "Sales Order", "Leave Application", "Leave Allocation", "Stock Entry"]
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype)
@@ -52,4 +55,4 @@ class AccountingPeriod(Document):
self.append('closed_documents', {
"document_type": doctype_for_closing.document_type,
"closed": doctype_for_closing.closed
})
})

View File

@@ -5,23 +5,42 @@ from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils import nowdate, add_months
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
# class TestAccountingPeriod(unittest.TestCase):
# def test_overlap(self):
# ap1 = create_accounting_period({"start_date":"2018-04-01", "end_date":"2018-06-30", "company":"Wind Power LLC"})
# ap1.save()
# ap2 = create_accounting_period({"start_date":"2018-06-30", "end_date":"2018-07-10", "company":"Wind Power LLC"})
# self.assertRaises(frappe.OverlapError, accounting_period_2.save())
#
# def tearDown(self):
# pass
#
#
# def create_accounting_period(**args):
# accounting_period = frappe.new_doc("Accounting Period")
# accounting_period.start_date = args.start_date or frappe.utils.datetime.date(2018, 4, 1)
# accounting_period.end_date = args.end_date or frappe.utils.datetime.date(2018, 6, 30)
# accounting_period.company = args.company
# accounting_period.period_name = "_Test_Period_Name_1"
#
# return accounting_period
class TestAccountingPeriod(unittest.TestCase):
def test_overlap(self):
ap1 = create_accounting_period(start_date = "2018-04-01",
end_date = "2018-06-30", company = "Wind Power LLC")
ap1.save()
ap2 = create_accounting_period(start_date = "2018-06-30",
end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
self.assertRaises(OverlapError, ap2.save)
def test_accounting_period(self):
ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
ap1.save()
doc = create_sales_invoice(do_not_submit=1, cost_center = "_Test Company - _TC", warehouse = "Stores - _TC")
self.assertRaises(ClosedAccountingPeriod, doc.submit)
def tearDown(self):
for d in frappe.get_all("Accounting Period"):
frappe.delete_doc("Accounting Period", d.name)
def create_accounting_period(**args):
args = frappe._dict(args)
accounting_period = frappe.new_doc("Accounting Period")
accounting_period.start_date = args.start_date or nowdate()
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
accounting_period.company = args.company or "_Test Company"
accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
accounting_period.append("closed_documents", {
"document_type": 'Sales Invoice', "closed": 1
})
return accounting_period

View File

@@ -10,9 +10,6 @@ def get_data():
{
'label': _('Bank Deatils'),
'items': ['Bank Account', 'Bank Guarantee']
},
{
'items': ['Payment Order']
}
]
}

View File

@@ -50,7 +50,7 @@ class BankTransaction(StatusUpdater):
if paid_amount and allocated_amount:
if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount):
frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).".format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount))))
elif flt(allocated_amount[0]["allocated_amount"]) == flt(paid_amount):
else:
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
self.clear_simple_entry(payment_entry)

View File

@@ -624,8 +624,8 @@ def get_outstanding_reference_documents(args):
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
if not data:
frappe.msgprint(_("No outstanding invoices found for the {0} <b>{1}</b>.")
.format(args.get("party_type").lower(), args.get("party")))
frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
.format(args.get("party_type").lower(), frappe.bold(args.get("party"))))
return data
@@ -648,13 +648,18 @@ def get_orders_to_be_billed(posting_date, party_type, party,
orders = []
if voucher_type:
ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
else:
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
orders = frappe.db.sql("""
select
name as voucher_no,
{ref_field} as invoice_amount,
({ref_field} - advance_paid) as outstanding_amount,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
transaction_date as posting_date
from
`tab{voucher_type}`
@@ -663,13 +668,14 @@ def get_orders_to_be_billed(posting_date, party_type, party,
and docstatus = 1
and company = %s
and ifnull(status, "") != "Closed"
and {ref_field} > advance_paid
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
and abs(100 - per_billed) > 0.01
{condition}
order by
transaction_date, name
""".format(**{
"ref_field": ref_field,
"rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
"condition": condition
@@ -677,8 +683,8 @@ def get_orders_to_be_billed(posting_date, party_type, party,
order_list = []
for d in orders:
if not (d.outstanding_amount >= filters.get("outstanding_amt_greater_than")
and d.outstanding_amount <= filters.get("outstanding_amt_less_than")):
if not (flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))):
continue
d["voucher_type"] = voucher_type
@@ -755,9 +761,23 @@ def get_party_details(company, party_type, party, date, cost_center=None):
@frappe.whitelist()
def get_account_details(account, date, cost_center=None):
frappe.has_permission('Payment Entry', throw=True)
# to check if the passed account is accessible under reference doctype Payment Entry
account_list = frappe.get_list('Account', {
'name': account
}, reference_doctype='Payment Entry', limit=1)
# There might be some user permissions which will allow account under certain doctypes
# except for Payment Entry, only in such case we should throw permission error
if not account_list:
frappe.throw(_('Account: {0} is not permitted under Payment Entry').format(account))
account_balance = get_balance_on(account, date, cost_center=cost_center,
ignore_account_permission=True)
return frappe._dict({
"account_currency": get_account_currency(account),
"account_balance": get_balance_on(account, date, cost_center=cost_center),
"account_balance": account_balance,
"account_type": frappe.db.get_value("Account", account, "account_type")
})

View File

@@ -66,6 +66,7 @@ frappe.ui.form.on('Payment Order', {
get_query_filters: {
bank: frm.doc.bank,
docstatus: 1,
payment_type: ("!=", "Receive"),
bank_account: frm.doc.company_bank_account,
paid_from: frm.doc.account,
payment_order_status: ["=", "Initiated"],

View File

@@ -93,7 +93,7 @@ class PaymentReconciliation(Document):
and `tab{doc}`.is_return = 1 and `tabGL Entry`.against_voucher_type = %(voucher_type)s
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
GROUP BY `tabSales Invoice`.name
GROUP BY `tab{doc}`.name
Having
amount > 0
""".format(doc=voucher_type, dr_or_cr=dr_or_cr, reconciled_dr_or_cr=reconciled_dr_or_cr), {
@@ -257,11 +257,8 @@ def reconcile_dr_cr_note(dr_cr_notes):
voucher_type = ('Credit Note'
if d.voucher_type == 'Sales Invoice' else 'Debit Note')
dr_or_cr = ('credit_in_account_currency'
if d.reference_type == 'Sales Invoice' else 'debit_in_account_currency')
reconcile_dr_or_cr = ('debit_in_account_currency'
if dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
jv = frappe.get_doc({
"doctype": "Journal Entry",
@@ -272,8 +269,7 @@ def reconcile_dr_cr_note(dr_cr_notes):
'account': d.account,
'party': d.party,
'party_type': d.party_type,
reconcile_dr_or_cr: (abs(d.allocated_amount)
if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
d.dr_or_cr: abs(d.allocated_amount),
'reference_type': d.against_voucher_type,
'reference_name': d.against_voucher
},
@@ -281,7 +277,8 @@ def reconcile_dr_cr_note(dr_cr_notes):
'account': d.account,
'party': d.party,
'party_type': d.party_type,
dr_or_cr: abs(d.allocated_amount),
reconcile_dr_or_cr: (abs(d.allocated_amount)
if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
'reference_type': d.voucher_type,
'reference_name': d.voucher_no
}

View File

@@ -382,7 +382,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
`tab{child_doc}`.amount
FROM `tab{child_doc}`, `tab{parent_doc}`
WHERE
`tab{child_doc}`.parent = `tab{parent_doc}`.name and {date_field}
`tab{child_doc}`.parent = `tab{parent_doc}`.name and `tab{parent_doc}`.{date_field}
between %s and %s and `tab{parent_doc}`.docstatus = 1
{condition} group by `tab{child_doc}`.name
""".format(parent_doc = doctype,

View File

@@ -307,7 +307,7 @@ def get_item_tax_data():
# example: {'Consulting Services': {'Excise 12 - TS': '12.000'}}
itemwise_tax = {}
taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax`""", as_dict=1)
taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax Template Detail`""", as_dict=1)
for tax in taxes:
if tax.parent not in itemwise_tax:

View File

@@ -44,6 +44,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
if (this.frm.doc.is_return) {
this.frm.return_print_format = "Sales Invoice Return";
}
this.show_general_ledger();
if(doc.update_stock) this.show_stock_ledger();
@@ -148,16 +152,24 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
set_default_print_format: function() {
// set default print format to POS type
// set default print format to POS type or Credit Note
if(cur_frm.doc.is_pos) {
if(cur_frm.pos_print_format) {
cur_frm.meta._default_print_format = cur_frm.meta.default_print_format;
cur_frm.meta.default_print_format = cur_frm.pos_print_format;
}
} else if(cur_frm.doc.is_return) {
if(cur_frm.return_print_format) {
cur_frm.meta._default_print_format = cur_frm.meta.default_print_format;
cur_frm.meta.default_print_format = cur_frm.return_print_format;
}
} else {
if(cur_frm.meta._default_print_format) {
cur_frm.meta.default_print_format = cur_frm.meta._default_print_format;
cur_frm.meta._default_print_format = null;
} else if(in_list([cur_frm.pos_print_format, cur_frm.return_print_format], cur_frm.meta.default_print_format)) {
cur_frm.meta.default_print_format = null;
cur_frm.meta._default_print_format = null;
}
}
},

View File

@@ -78,6 +78,7 @@ class SalesInvoice(SellingController):
self.so_dn_required()
self.validate_proj_cust()
self.validate_pos_return()
self.validate_with_previous_doc()
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_uom_is_integer("uom", "qty")
@@ -199,6 +200,16 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_submit")
def validate_pos_return(self):
if self.is_pos and self.is_return:
total_amount_in_payments = 0
for payment in self.payments:
total_amount_in_payments += payment.amount
if total_amount_in_payments < self.rounded_total:
frappe.throw(_("Total payments amount can't be greater than {}".format(-self.rounded_total)))
def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice."))

View File

@@ -10,11 +10,13 @@ from erpnext.accounts.doctype.budget.budget import validate_expense_against_budg
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class ClosedAccountingPeriod(frappe.ValidationError): pass
class StockAccountInvalidTransaction(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
if gl_map:
if not cancel:
validate_accounting_period(gl_map)
gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1:
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
@@ -23,6 +25,27 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd
else:
delete_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
def validate_accounting_period(gl_map):
accounting_periods = frappe.db.sql(""" SELECT
ap.name as name
FROM
`tabAccounting Period` ap, `tabClosed Document` cd
WHERE
ap.name = cd.parent
AND ap.company = %(company)s
AND cd.closed = 1
AND cd.document_type = %(voucher_type)s
AND %(date)s between ap.start_date and ap.end_date
""", {
'date': gl_map[0].posting_date,
'company': gl_map[0].company,
'voucher_type': gl_map[0].voucher_type
}, as_dict=1)
if accounting_periods:
frappe.throw(_("You can't create accounting entries in the closed accounting period {0}")
.format(accounting_periods[0].name), ClosedAccountingPeriod)
def process_gl_map(gl_map, merge_entries=True):
if merge_entries:
gl_map = merge_similar_entries(gl_map)
@@ -93,6 +116,7 @@ def check_if_in_list(gle, gl_map, dimensions=None):
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
validate_account_for_perpetual_inventory(gl_map)
validate_cwip_accounts(gl_map)
round_off_debit_credit(gl_map)
@@ -123,6 +147,16 @@ def validate_account_for_perpetual_inventory(gl_map):
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
.format(entry.account), StockAccountInvalidTransaction)
def validate_cwip_accounts(gl_map):
if not cint(frappe.db.get_value("Asset Settings", None, "disable_cwip_accounting")) \
and gl_map[0].voucher_type == "Journal Entry":
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
where account_type = 'Capital Work in Progress' and is_group=0""")]
for entry in gl_map:
if entry.account in cwip_accounts:
frappe.throw(_("Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
def round_off_debit_credit(gl_map):
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))

View File

@@ -469,7 +469,9 @@ def get_timeline_data(doctype, name):
# fetch and append data from Activity Log
data += frappe.db.sql("""select {fields}
from `tabActivity Log`
where reference_doctype={doctype} and reference_name={name}
where (reference_doctype="{doctype}" and reference_name="{name}")
or (timeline_doctype in ("{doctype}") and timeline_name="{name}")
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name="{name}")
and status!='Success' and creation > {after}
{group_by} order by creation desc
""".format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields,

View File

@@ -0,0 +1,129 @@
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value, fieldmeta,
get_width, get_align_class -%}
{%- macro render_currency(df, doc) -%}
<div class="row {% if df.bold %}important{% endif %} data-field">
<div class="col-xs-{{ "9" if df.fieldtype=="Check" else "5" }}
{%- if doc.align_labels_right %} text-right{%- endif -%}">
<label>{{ _(df.label) }}</label>
</div>
<div class="col-xs-{{ "3" if df.fieldtype=="Check" else "7" }} value">
{% if doc.get(df.fieldname) != None -%}
{{ frappe.utils.fmt_money((doc[df.fieldname])|int|abs, currency=doc.currency) }}
{% endif %}
</div>
</div>
{%- endmacro -%}
{%- macro render_taxes(df, doc) -%}
{%- set data = doc.get(df.fieldname)[df.start:df.end] -%}
<div class="row">
<div class="col-xs-6"></div>
<div class="col-xs-6">
{%- for charge in data -%}
{%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
<div class="row">
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
<label>{{ charge.get_formatted("description") }}</label></div>
<div class="col-xs-7 text-right">
{{ frappe.utils.fmt_money((charge.tax_amount)|int|abs, currency=doc.currency) }}
</div>
</div>
{%- endif -%}
{%- endfor -%}
</div>
</div>
{%- endmacro -%}
{%- macro render_table(df, doc) -%}
{%- set table_meta = frappe.get_meta(df.options) -%}
{%- set data = doc.get(df.fieldname)[df.start:df.end] -%}
{%- if doc.print_templates and
doc.print_templates.get(df.fieldname) -%}
{% include doc.print_templates[df.fieldname] %}
{%- else -%}
{%- if data -%}
{%- set visible_columns = get_visible_columns(doc.get(df.fieldname),
table_meta, df) -%}
<div {{ fieldmeta(df) }}>
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th style="width: 40px" class="table-sr">{{ _("Sr") }}</th>
{% for tdf in visible_columns %}
{% if (data and not data[0].flags.compact_item_print) or tdf.fieldname in doc.get(df.fieldname)[0].flags.compact_item_fields %}
<th style="width: {{ get_width(tdf) }};" class="{{ get_align_class(tdf) }}" {{ fieldmeta(df) }}>
{{ _(tdf.label) }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for d in data %}
<tr>
<td class="table-sr">{{ d.idx }}</td>
{% for tdf in visible_columns %}
{% if not d.flags.compact_item_print or tdf.fieldname in doc.get(df.fieldname)[0].flags.compact_item_fields %}
<td class="{{ get_align_class(tdf) }}" {{ fieldmeta(df) }}>
{% if tdf.fieldtype == 'Currency' %}
<div class="value">{{ frappe.utils.fmt_money((d[tdf.fieldname])|int|abs, currency=doc.currency) }}</div></td>
{% else %}
<div class="value">{{ print_value(tdf, d, doc, visible_columns) }}</div></td>
{% endif %}
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{%- endif -%}
{%- endif -%}
{%- endmacro -%}
{% for page in layout %}
<div class="page-break">
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
</div>
{% if print_settings.repeat_header_footer %}
<div id="footer-html" class="visible-pdf">
{% if not no_letterhead and footer %}
<div class="letter-head-footer">
{{ footer }}
</div>
{% endif %}
<p class="text-center small page-number visible-pdf">
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
</p>
</div>
{% endif %}
{% for section in page %}
<div class="row section-break">
{% if section.columns.fields %}
{%- if doc.print_line_breaks and loop.index != 1 -%}<hr>{%- endif -%}
{%- if doc.print_section_headings and section.label and section.has_data -%}
<h4 class='col-sm-12'>{{ _(section.label) }}</h4>
{% endif %}
{%- endif -%}
{% for column in section.columns %}
<div class="col-xs-{{ (12 / section.columns|len)|int }} column-break">
{% for df in column.fields %}
{% if df.fieldname == 'taxes' %}
{{ render_taxes(df, doc) }}
{% elif df.fieldtype == 'Currency' %}
{{ render_currency(df, doc) }}
{% elif df.fieldtype =='Table' %}
{{ render_table(df, doc)}}
{% elif doc[df.fieldname] %}
{{ render_field(df, doc) }}
{% endif %}
{% endfor %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endfor %}

View File

@@ -0,0 +1,24 @@
{
"align_labels_right": 1,
"creation": "2019-07-24 20:13:30.259953",
"custom_format": 0,
"default_print_language": "en-US",
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"html": "",
"idx": 0,
"line_breaks": 1,
"modified": "2019-07-24 20:13:30.259953",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Return",
"owner": "Administrator",
"print_format_builder": 0,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 1,
"standard": "Yes"
}

View File

@@ -33,6 +33,9 @@ class ReceivablePayableReport(object):
columns += [_(args.get("party_type")) + ":Link/" + args.get("party_type") + ":200"]
if party_naming_by == "Naming Series":
columns += [args.get("party_type") + " Name::110"]
if args.get("party_type") == 'Customer':
columns.append({
"label": _("Customer Contact"),
@@ -42,9 +45,6 @@ class ReceivablePayableReport(object):
"width": 100
})
if party_naming_by == "Naming Series":
columns += [args.get("party_type") + " Name::110"]
columns.append({
"label": _("Voucher Type"),
"fieldtype": "Data",
@@ -197,8 +197,10 @@ class ReceivablePayableReport(object):
if self.filters.based_on_payment_terms and gl_entries_data:
self.payment_term_map = self.get_payment_term_detail(voucher_nos)
self.gle_inclusion_map = {}
for gle in gl_entries_data:
if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers, return_entries):
self.gle_inclusion_map[gle.name] = True
outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount(
gle,self.filters.report_date, self.dr_or_cr, return_entries)
temp_outstanding_amt = outstanding_amount
@@ -409,7 +411,9 @@ class ReceivablePayableReport(object):
for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no):
if getdate(e.posting_date) <= report_date \
and (e.name!=gle.name or (e.voucher_no in return_entries and not return_entries.get(e.voucher_no))):
if e.name!=gle.name and self.gle_inclusion_map.get(e.name):
continue
self.gle_inclusion_map[e.name] = True
amount = flt(e.get(reverse_dr_or_cr), self.currency_precision) - flt(e.get(dr_or_cr), self.currency_precision)
if e.voucher_no not in return_entries:
payment_amount += amount

View File

@@ -425,9 +425,12 @@ def get_cost_centers_with_children(cost_centers):
all_cost_centers = []
for d in cost_centers:
lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"])
children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
all_cost_centers += [c.name for c in children]
if frappe.db.exists("Cost Center", d):
lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"])
children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
all_cost_centers += [c.name for c in children]
else:
frappe.throw(_("Cost Center: {0} does not exist".format(d)))
return list(set(all_cost_centers))

View File

@@ -119,19 +119,11 @@ def get_gl_entries(filters):
select_fields = """, debit, credit, debit_in_account_currency,
credit_in_account_currency """
group_by_statement = ''
order_by_statement = "order by posting_date, account"
if filters.get("group_by") == _("Group by Voucher"):
order_by_statement = "order by posting_date, voucher_type, voucher_no"
if filters.get("group_by") == _("Group by Voucher (Consolidated)"):
group_by_statement = "group by voucher_type, voucher_no, account, cost_center"
select_fields = """, sum(debit) as debit, sum(credit) as credit,
sum(debit_in_account_currency) as debit_in_account_currency,
sum(credit_in_account_currency) as credit_in_account_currency"""
if filters.get("include_default_book_entries"):
filters['company_fb'] = frappe.db.get_value("Company",
filters.get("company"), 'default_finance_book')
@@ -144,11 +136,10 @@ def get_gl_entries(filters):
against_voucher_type, against_voucher, account_currency,
remarks, against, is_opening {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions} {group_by_statement}
where company=%(company)s {conditions}
{order_by_statement}
""".format(
select_fields=select_fields, conditions=get_conditions(filters),
group_by_statement=group_by_statement,
order_by_statement=order_by_statement
),
filters, as_dict=1)
@@ -185,7 +176,8 @@ def get_conditions(filters):
if not (filters.get("account") or filters.get("party") or
filters.get("group_by") in ["Group by Account", "Group by Party"]):
conditions.append("posting_date >=%(from_date)s")
conditions.append("posting_date <=%(to_date)s")
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
if filters.get("project"):
conditions.append("project in %(project)s")
@@ -286,6 +278,7 @@ def initialize_gle_map(gl_entries, filters):
def get_accountwise_gle(filters, gl_entries, gle_map):
totals = get_totals_dict()
entries = []
consolidated_gle = OrderedDict()
group_by = group_by_field(filters.get('group_by'))
def update_value_in_dict(data, key, gle):
@@ -310,12 +303,20 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
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)
else:
entries.append(gle)
elif filters.get("group_by") == _('Group by Voucher (Consolidated)'):
key = (gle.get("voucher_type"), gle.get("voucher_no"),
gle.get("account"), gle.get("cost_center"))
if key not in consolidated_gle:
consolidated_gle.setdefault(key, gle)
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():
entries.append(value)
return totals, entries
def get_result_as_list(data, filters):

View File

@@ -84,7 +84,8 @@ def validate_fiscal_year(date, fiscal_year, company, label="Date", doc=None):
throw(_("{0} '{1}' not in Fiscal Year {2}").format(label, formatdate(date), fiscal_year))
@frappe.whitelist()
def get_balance_on(account=None, date=None, party_type=None, party=None, company=None, in_account_currency=True, cost_center=None):
def get_balance_on(account=None, date=None, party_type=None, party=None, company=None,
in_account_currency=True, cost_center=None, ignore_account_permission=False):
if not account and frappe.form_dict.get("account"):
account = frappe.form_dict.get("account")
if not date and frappe.form_dict.get("date"):
@@ -140,7 +141,8 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
if account:
if not frappe.flags.ignore_account_permission:
if not (frappe.flags.ignore_account_permission
or ignore_account_permission):
acc.check_permission("read")
if report_type == 'Profit and Loss':

View File

@@ -483,7 +483,7 @@ def make_rm_stock_entry(purchase_order, rm_items):
'from_warehouse': rm_item_data["warehouse"],
'stock_uom': rm_item_data["stock_uom"],
'main_item_code': rm_item_data["item_code"],
'allow_alternative_item': item_wh[rm_item_code].get('allow_alternative_item')
'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item')
}
}
stock_entry.add_to_stock_entry_detail(items_dict)

View File

@@ -30,7 +30,9 @@ def update_last_purchase_rate(doc, is_submit):
# for it to be considered for latest purchase rate
if flt(d.conversion_factor):
last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor)
else:
# Check if item code is present
# Conversion factor should not be mandatory for non itemized items
elif d.item_code:
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
# update last purchsae rate
@@ -84,13 +86,13 @@ def get_linked_material_requests(items):
items = json.loads(items)
mr_list = []
for item in items:
material_request = frappe.db.sql("""SELECT distinct mr.name AS mr_name,
(mr_item.qty - mr_item.ordered_qty) AS qty,
material_request = frappe.db.sql("""SELECT distinct mr.name AS mr_name,
(mr_item.qty - mr_item.ordered_qty) AS qty,
mr_item.item_code AS item_code,
mr_item.name AS mr_item
mr_item.name AS mr_item
FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
WHERE mr.name = mr_item.parent
AND mr_item.item_code = %(item)s
AND mr_item.item_code = %(item)s
AND mr.material_request_type = 'Purchase'
AND mr.per_ordered < 99.99
AND mr.docstatus = 1
@@ -98,6 +100,6 @@ def get_linked_material_requests(items):
ORDER BY mr_item.item_code ASC""",{"item": item}, as_dict=1)
if material_request:
mr_list.append(material_request)
return mr_list

View File

@@ -8,12 +8,18 @@
"from",
"to",
"column_break_3",
"received_by",
"medium",
"caller_information",
"contact",
"contact_name",
"column_break_10",
"lead",
"lead_name",
"section_break_5",
"status",
"duration",
"recording_url",
"summary"
"recording_url"
],
"fields": [
{
@@ -60,12 +66,6 @@
"label": "Duration",
"read_only": 1
},
{
"fieldname": "summary",
"fieldtype": "Data",
"label": "Summary",
"read_only": 1
},
{
"fieldname": "recording_url",
"fieldtype": "Data",
@@ -77,10 +77,58 @@
"fieldtype": "Data",
"label": "Medium",
"read_only": 1
},
{
"fieldname": "received_by",
"fieldtype": "Link",
"label": "Received By",
"options": "Employee",
"read_only": 1
},
{
"fieldname": "caller_information",
"fieldtype": "Section Break",
"label": "Caller Information"
},
{
"fieldname": "contact",
"fieldtype": "Link",
"label": "Contact",
"options": "Contact",
"read_only": 1
},
{
"fieldname": "lead",
"fieldtype": "Link",
"label": "Lead ",
"options": "Lead",
"read_only": 1
},
{
"fetch_from": "contact.name",
"fieldname": "contact_name",
"fieldtype": "Data",
"hidden": 1,
"in_list_view": 1,
"label": "Contact Name",
"read_only": 1
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"fetch_from": "lead.lead_name",
"fieldname": "lead_name",
"fieldtype": "Data",
"hidden": 1,
"in_list_view": 1,
"label": "Lead Name",
"read_only": 1
}
],
"in_create": 1,
"modified": "2019-07-01 09:09:48.516722",
"modified": "2019-08-06 05:46:53.144683",
"modified_by": "Administrator",
"module": "Communication",
"name": "Call Log",
@@ -97,10 +145,15 @@
"role": "System Manager",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "Employee"
}
],
"sort_field": "modified",
"sort_order": "ASC",
"title_field": "from",
"track_changes": 1
"track_changes": 1,
"track_views": 1
}

View File

@@ -4,16 +4,88 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from erpnext.crm.doctype.utils import get_employee_emails_for_popup
from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup, strip_number
from frappe.contacts.doctype.contact.contact import get_contact_with_phone_number
from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number
class CallLog(Document):
def before_insert(self):
number = strip_number(self.get('from'))
self.contact = get_contact_with_phone_number(number)
self.lead = get_lead_with_phone_number(number)
def after_insert(self):
employee_emails = get_employee_emails_for_popup(self.medium)
for email in employee_emails:
frappe.publish_realtime('show_call_popup', self, user=email)
self.trigger_call_popup()
def on_update(self):
doc_before_save = self.get_doc_before_save()
if doc_before_save and doc_before_save.status in ['Ringing'] and self.status in ['Missed', 'Completed']:
if not doc_before_save: return
if doc_before_save.status in ['Ringing'] and self.status in ['Missed', 'Completed']:
frappe.publish_realtime('call_{id}_disconnected'.format(id=self.id), self)
elif doc_before_save.to != self.to:
self.trigger_call_popup()
def trigger_call_popup(self):
scheduled_employees = get_scheduled_employees_for_popup(self.to)
employee_emails = get_employees_with_number(self.to)
# check if employees with matched number are scheduled to receive popup
emails = set(scheduled_employees).intersection(employee_emails)
# # if no employee found with matching phone number then show popup to scheduled employees
# emails = emails or scheduled_employees if employee_emails
for email in emails:
frappe.publish_realtime('show_call_popup', self, user=email)
@frappe.whitelist()
def add_call_summary(call_log, summary):
doc = frappe.get_doc('Call Log', call_log)
doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '<br><br>' + summary)
def get_employees_with_number(number):
number = strip_number(number)
if not number: return []
employee_emails = frappe.cache().hget('employees_with_number', number)
if employee_emails: return employee_emails
employees = frappe.get_all('Employee', filters={
'cell_number': ['like', '%{}%'.format(number)],
'user_id': ['!=', '']
}, fields=['user_id'])
employee_emails = [employee.user_id for employee in employees]
frappe.cache().hset('employees_with_number', number, employee_emails)
return employee
def set_caller_information(doc, state):
'''Called from hooks on creation of Lead or Contact'''
if doc.doctype not in ['Lead', 'Contact']: return
numbers = [doc.get('phone'), doc.get('mobile_no')]
# contact for Contact and lead for Lead
fieldname = doc.doctype.lower()
# contact_name or lead_name
display_name_field = '{}_name'.format(fieldname)
for number in numbers:
number = strip_number(number)
if not number: continue
filters = frappe._dict({
'from': ['like', '%{}'.format(number)],
fieldname: ''
})
logs = frappe.get_all('Call Log', filters=filters)
for log in logs:
frappe.db.set_value('Call Log', log.name, {
fieldname: doc.name,
display_name_field: doc.get_title()
}, update_modified=False)

View File

@@ -134,6 +134,12 @@ def get_data():
"name": "Employee Leave Balance",
"doctype": "Leave Application"
},
{
"type": "report",
"is_query_report": True,
"name": "Leave Ledger Entry",
"doctype": "Leave Ledger Entry"
},
]
},
{
@@ -213,6 +219,16 @@ def get_data():
"name": "Employee Benefit Claim",
"dependencies": ["Employee"]
},
{
"type": "doctype",
"name": "Employee Tax Exemption Category",
"dependencies": ["Employee"]
},
{
"type": "doctype",
"name": "Employee Tax Exemption Sub Category",
"dependencies": ["Employee"]
},
]
},
{

View File

@@ -94,6 +94,13 @@ def get_data():
"name": "BOM Update Tool",
"description": _("Replace BOM and update latest price in all BOMs"),
},
{
"type": "page",
"label": _("BOM Comparison Tool"),
"name": "bom-comparison-tool",
"description": _("Compare BOMs for changes in Raw Materials and Operations"),
"data_doctype": "BOM"
},
]
},
{

View File

@@ -60,7 +60,9 @@ class AccountsController(TransactionBase):
def validate(self):
self.validate_qty_is_not_zero()
if not self.get('is_return'):
self.validate_qty_is_not_zero()
if self.get("_action") and self._action != "update_after_submit":
self.set_missing_values(for_validate=True)
@@ -1190,6 +1192,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
.format(child_item.idx, child_item.item_code))
else:
child_item.rate = flt(d.get("rate"))
if flt(child_item.price_list_rate):
child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0, \
child_item.precision("discount_percentage"))
child_item.flags.ignore_validate_update_after_submit = True
if new_child_flag:
child_item.idx = len(parent.items) + 1

View File

@@ -395,7 +395,9 @@ class BuyingController(StockController):
def set_qty_as_per_stock_uom(self):
for d in self.get("items"):
if d.meta.get_field("stock_qty"):
if not d.conversion_factor:
# Check if item code is present
# Conversion factor should not be mandatory for non itemized items
if not d.conversion_factor and d.item_code:
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
@@ -725,7 +727,7 @@ def get_items_from_bom(item_code, bom, exploded_item=1):
where
t2.parent = t1.name and t1.item = %s
and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
and t2.item_code = t3.name and t3.is_stock_item = 1""".format(doctype),
and t2.item_code = t3.name""".format(doctype),
(item_code, bom), as_dict=1)
if not bom_items:

View File

@@ -371,7 +371,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select tabAccount.name from `tabAccount`
where (tabAccount.report_type = "Profit and Loss"
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed"))
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
and tabAccount.is_group=0
and tabAccount.docstatus!=2
and tabAccount.{key} LIKE %(txt)s

View File

@@ -18,34 +18,31 @@ def validate_return(doc):
validate_returned_items(doc)
def validate_return_against(doc):
filters = {"doctype": doc.doctype, "docstatus": 1, "company": doc.company}
if doc.meta.get_field("customer") and doc.customer:
filters["customer"] = doc.customer
elif doc.meta.get_field("supplier") and doc.supplier:
filters["supplier"] = doc.supplier
if not frappe.db.exists(filters):
if not frappe.db.exists(doc.doctype, doc.return_against):
frappe.throw(_("Invalid {0}: {1}")
.format(doc.meta.get_label("return_against"), doc.return_against))
else:
ref_doc = frappe.get_doc(doc.doctype, doc.return_against)
# validate posting date time
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier"
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
if ref_doc.company == doc.company and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.docstatus == 1:
# validate posting date time
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
# validate same exchange rate
if doc.conversion_rate != ref_doc.conversion_rate:
frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
.format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
# validate update stock
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
.format(doc.return_against))
# validate same exchange rate
if doc.conversion_rate != ref_doc.conversion_rate:
frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
.format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
# validate update stock
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
.format(doc.return_against))
def validate_returned_items(doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos

View File

@@ -81,7 +81,12 @@ class calculate_taxes_and_totals(object):
item.discount_amount = item.price_list_rate - item.rate
item.net_rate = item.rate
item.amount = flt(item.rate * item.qty, item.precision("amount"))
if not item.qty and self.doc.get("is_return"):
item.amount = flt(-1 * item.rate, item.precision("amount"))
else:
item.amount = flt(item.rate * item.qty, item.precision("amount"))
item.net_amount = item.amount
self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])

View File

@@ -21,42 +21,45 @@ def get_list_context(context=None):
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
user = frappe.session.user
key = None
ignore_permissions = False
if not filters: filters = []
if doctype == 'Supplier Quotation':
filters.append((doctype, "docstatus", "<", 2))
filters.append((doctype, 'docstatus', '<', 2))
else:
filters.append((doctype, "docstatus", "=", 1))
filters.append((doctype, 'docstatus', '=', 1))
if (user != "Guest" and is_website_user()) or doctype == 'Request for Quotation':
if (user != 'Guest' and is_website_user()) or doctype == 'Request for Quotation':
parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype
# find party for this contact
customers, suppliers = get_customers_suppliers(parties_doctype, user)
if not customers and not suppliers: return []
key, parties = get_party_details(customers, suppliers)
if doctype == 'Request for Quotation':
return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length)
filters.append((doctype, key, "in", parties))
if key:
return post_process(doctype, get_list_for_transactions(doctype, txt,
filters=filters, fields="name",limit_start=limit_start,
limit_page_length=limit_page_length,ignore_permissions=True,
order_by="modified desc"))
if customers:
if doctype == 'Quotation':
filters.append(('quotation_to', '=', 'Customer'))
filters.append(('party_name', 'in', customers))
else:
filters.append(('customer', 'in', customers))
elif suppliers:
filters.append(('supplier', 'in', suppliers))
else:
return []
return post_process(doctype, get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
fields="name", order_by="modified desc"))
if doctype == 'Request for Quotation':
parties = customers or suppliers
return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length)
# Since customers and supplier do not have direct access to internal doctypes
ignore_permissions = True
transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
return post_process(doctype, transactions)
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,
ignore_permissions=False,fields=None, order_by=None):
ignore_permissions=False, fields=None, order_by=None):
""" Get List of transactions like Invoices, Orders """
from frappe.www.list import get_list
meta = frappe.get_meta(doctype)
@@ -77,22 +80,12 @@ def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_len
if or_filters:
for r in frappe.get_list(doctype, fields=fields,filters=filters, or_filters=or_filters,
limit_start=limit_start, limit_page_length=limit_page_length,
limit_start=limit_start, limit_page_length=limit_page_length,
ignore_permissions=ignore_permissions, order_by=order_by):
data.append(r)
return data
def get_party_details(customers, suppliers):
if customers:
key, parties = "customer", customers
elif suppliers:
key, parties = "supplier", suppliers
else:
key, parties = "customer", []
return key, parties
def rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length):
data = frappe.db.sql("""select distinct parent as name, supplier from `tab{doctype}`
where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""".
@@ -130,38 +123,56 @@ def get_customers_suppliers(doctype, user):
suppliers = []
meta = frappe.get_meta(doctype)
customer_field_name = get_customer_field_name(doctype)
has_customer_field = meta.has_field(customer_field_name)
has_supplier_field = meta.has_field('supplier')
if has_common(["Supplier", "Customer"], frappe.get_roles(user)):
contacts = frappe.db.sql("""
select
select
`tabContact`.email_id,
`tabDynamic Link`.link_doctype,
`tabDynamic Link`.link_name
from
from
`tabContact`, `tabDynamic Link`
where
`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s
""", user, as_dict=1)
customers = [c.link_name for c in contacts if c.link_doctype == 'Customer'] \
if meta.get_field("customer") else None
suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier'] \
if meta.get_field("supplier") else None
customers = [c.link_name for c in contacts if c.link_doctype == 'Customer']
suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier']
elif frappe.has_permission(doctype, 'read', user=user):
customers = [customer.name for customer in frappe.get_list("Customer")] \
if meta.get_field("customer") else None
suppliers = [supplier.name for supplier in frappe.get_list("Customer")] \
if meta.get_field("supplier") else None
customer_list = frappe.get_list("Customer")
customers = suppliers = [customer.name for customer in customer_list]
return customers, suppliers
return customers if has_customer_field else None, \
suppliers if has_supplier_field else None
def has_website_permission(doc, ptype, user, verbose=False):
doctype = doc.doctype
customers, suppliers = get_customers_suppliers(doctype, user)
if customers:
return frappe.get_all(doctype, filters=[(doctype, "customer", "in", customers),
(doctype, "name", "=", doc.name)]) and True or False
return frappe.db.exists(doctype, get_customer_filter(doc, customers))
elif suppliers:
fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier'
return frappe.get_all(doctype, filters=[(doctype, fieldname, "in", suppliers),
(doctype, "name", "=", doc.name)]) and True or False
return frappe.db.exists(doctype, filters={
'name': doc.name,
fieldname: ["in", suppliers]
})
else:
return False
def get_customer_filter(doc, customers):
doctype = doc.doctype
filters = frappe._dict()
filters.name = doc.name
filters[get_customer_field_name(doctype)] = ['in', customers]
if doctype == 'Quotation':
filters.quotation_to = 'Customer'
return filters
def get_customer_field_name(doctype):
if doctype == 'Quotation':
return 'party_name'
else:
return 'customer'

View File

@@ -145,6 +145,16 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False):
@frappe.whitelist()
def make_opportunity(source_name, target_doc=None):
def set_missing_values(source, target):
address = frappe.get_all('Dynamic Link', {
'link_doctype': source.doctype,
'link_name': source.name,
'parenttype': 'Address',
}, ['parent'], limit=1)
if address:
target.customer_address = address[0].parent
target_doc = get_mapped_doc("Lead", source_name,
{"Lead": {
"doctype": "Opportunity",
@@ -157,7 +167,7 @@ def make_opportunity(source_name, target_doc=None):
"email_id": "contact_email",
"mobile_no": "contact_mobile"
}
}}, target_doc)
}}, target_doc, set_missing_values)
return target_doc
@@ -230,3 +240,15 @@ def make_lead_from_communication(communication, ignore_communication_links=False
link_communication_to_document(doc, "Lead", lead_name, ignore_communication_links)
return lead_name
def get_lead_with_phone_number(number):
if not number: return
leads = frappe.get_all('Lead', or_filters={
'phone': ['like', '%{}'.format(number)],
'mobile_no': ['like', '%{}'.format(number)]
}, limit=1)
lead = leads[0].name if leads else None
return lead

View File

@@ -31,9 +31,9 @@ frappe.ui.form.on("Opportunity", {
party_name: function(frm) {
frm.toggle_display("contact_info", frm.doc.party_name);
frm.trigger('set_contact_link');
if (frm.doc.opportunity_from == "Customer") {
frm.trigger('set_contact_link');
erpnext.utils.get_party_details(frm);
} else if (frm.doc.opportunity_from == "Lead") {
erpnext.utils.map_current_doc({
@@ -48,13 +48,6 @@ frappe.ui.form.on("Opportunity", {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
party_name: function(frm) {
if (frm.doc.opportunity_from == "Customer") {
frm.trigger('set_contact_link');
erpnext.utils.get_party_details(frm);
}
},
with_items: function(frm) {
frm.trigger('toggle_mandatory');
},

View File

@@ -3,82 +3,59 @@ from frappe import _
import json
@frappe.whitelist()
def get_document_with_phone_number(number):
# finds contacts and leads
if not number: return
number = number.lstrip('0')
number_filter = {
'phone': ['like', '%{}'.format(number)],
'mobile_no': ['like', '%{}'.format(number)]
}
contacts = frappe.get_all('Contact', or_filters=number_filter, limit=1)
def get_last_interaction(contact=None, lead=None):
if contacts:
return frappe.get_doc('Contact', contacts[0].name)
if not contact and not lead: return
leads = frappe.get_all('Lead', or_filters=number_filter, limit=1)
if leads:
return frappe.get_doc('Lead', leads[0].name)
@frappe.whitelist()
def get_last_interaction(number, reference_doc):
reference_doc = json.loads(reference_doc) if reference_doc else get_document_with_phone_number(number)
if not reference_doc: return
reference_doc = frappe._dict(reference_doc)
last_communication = {}
last_issue = {}
if reference_doc.doctype == 'Contact':
customer_name = ''
last_communication = None
last_issue = None
if contact:
query_condition = ''
for link in reference_doc.links:
link = frappe._dict(link)
values = []
contact = frappe.get_doc('Contact', contact)
for link in contact.links:
if link.link_doctype == 'Customer':
customer_name = link.link_name
query_condition += "(`reference_doctype`='{}' AND `reference_name`='{}') OR".format(link.link_doctype, link.link_name)
last_issue = get_last_issue_from_customer(link.link_name)
query_condition += "(`reference_doctype`=%s AND `reference_name`=%s) OR"
values += [link_link_doctype, link_link_name]
if query_condition:
# remove extra appended 'OR'
query_condition = query_condition[:-2]
last_communication = frappe.db.sql("""
SELECT `name`, `content`
FROM `tabCommunication`
WHERE {}
WHERE `sent_or_received`='Received'
AND ({})
ORDER BY `modified`
LIMIT 1
""".format(query_condition)) # nosec
""".format(query_condition), values, as_dict=1) # nosec
if customer_name:
last_issue = frappe.get_all('Issue', {
'customer': customer_name
}, ['name', 'subject', 'customer'], limit=1)
elif reference_doc.doctype == 'Lead':
if lead:
last_communication = frappe.get_all('Communication', filters={
'reference_doctype': reference_doc.doctype,
'reference_name': reference_doc.name,
'reference_doctype': 'Lead',
'reference_name': lead,
'sent_or_received': 'Received'
}, fields=['name', 'content'], limit=1)
}, fields=['name', 'content'], order_by='`creation` DESC', limit=1)
last_communication = last_communication[0] if last_communication else None
return {
'last_communication': last_communication[0] if last_communication else None,
'last_issue': last_issue[0] if last_issue else None
'last_communication': last_communication,
'last_issue': last_issue
}
@frappe.whitelist()
def add_call_summary(docname, summary):
call_log = frappe.get_doc('Call Log', docname)
summary = _('Call Summary by {0}: {1}').format(
frappe.utils.get_fullname(frappe.session.user), summary)
if not call_log.summary:
call_log.summary = summary
else:
call_log.summary += '<br>' + summary
call_log.save(ignore_permissions=True)
def get_last_issue_from_customer(customer_name):
issues = frappe.get_all('Issue', {
'customer': customer_name
}, ['name', 'subject', 'customer'], order_by='`creation` DESC', limit=1)
return issues[0] if issues else None
def get_scheduled_employees_for_popup(communication_medium):
if not communication_medium: return []
def get_employee_emails_for_popup(communication_medium):
now_time = frappe.utils.nowtime()
weekday = frappe.utils.get_weekday()
@@ -98,3 +75,10 @@ def get_employee_emails_for_popup(communication_medium):
employee_emails = set([employee.user_id for employee in employees])
return employee_emails
def strip_number(number):
if not number: return
# strip 0 from the start of the number for proper number comparisions
# eg. 07888383332 should match with 7888383332
number = number.lstrip('0')
return number

View File

@@ -1,843 +1,213 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 1,
"creation": "2018-07-10 14:48:16.757030",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"status",
"application_settings",
"client_id",
"redirect_url",
"token_endpoint",
"application_column_break",
"client_secret",
"scope",
"api_endpoint",
"authorization_settings",
"authorization_endpoint",
"refresh_token",
"code",
"authorization_column_break",
"authorization_url",
"access_token",
"quickbooks_company_id",
"company_settings",
"company",
"default_shipping_account",
"default_warehouse",
"company_column_break",
"default_cost_center",
"undeposited_funds_account"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "Connecting to QuickBooks\nConnected to QuickBooks\nIn Progress\nComplete\nFailed",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Connecting to QuickBooks\nConnected to QuickBooks\nIn Progress\nComplete\nFailed"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "eval:doc.client_id && doc.client_secret && doc.redirect_url",
"columns": 0,
"fieldname": "application_settings",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Application Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Application Settings"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "client_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Client ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "redirect_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Redirect URL",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
"fieldname": "token_endpoint",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Token Endpoint",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "application_column_break",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "client_secret",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Client Secret",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "com.intuit.quickbooks.accounting",
"fieldname": "scope",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Scope",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "https://quickbooks.api.intuit.com/v3",
"fieldname": "api_endpoint",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "API Endpoint",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "authorization_settings",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Authorization Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Authorization Settings"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "https://appcenter.intuit.com/connect/oauth2",
"fieldname": "authorization_endpoint",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Authorization Endpoint",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "refresh_token",
"fieldtype": "Small Text",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Refresh Token",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Refresh Token"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "code",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Code",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Code"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "authorization_column_break",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "authorization_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Authorization URL",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "access_token",
"fieldtype": "Small Text",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Access Token",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Access Token"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "quickbooks_company_id",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Quickbooks Company ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Quickbooks Company ID"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company_settings",
"fieldtype": "Section Break",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Company Settings"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Company"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_shipping_account",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Shipping Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Account"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_warehouse",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Warehouse",
"length": 0,
"no_copy": 0,
"options": "Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Warehouse"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company_column_break",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_cost_center",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Cost Center",
"length": 0,
"no_copy": 0,
"options": "Cost Center",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Cost Center"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "undeposited_funds_account",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Undeposited Funds Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "Account"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-17 03:12:53.506229",
"modified": "2019-08-07 15:26:00.653433",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "QuickBooks Migrator",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
"sort_order": "DESC"
}

View File

@@ -18,6 +18,8 @@ def handle_incoming_call(**kwargs):
call_log = get_call_log(call_payload)
if not call_log:
create_call_log(call_payload)
else:
update_call_log(call_payload, call_log=call_log)
@frappe.whitelist(allow_guest=True)
def handle_end_call(**kwargs):
@@ -27,10 +29,11 @@ def handle_end_call(**kwargs):
def handle_missed_call(**kwargs):
update_call_log(kwargs, 'Missed')
def update_call_log(call_payload, status):
call_log = get_call_log(call_payload)
def update_call_log(call_payload, status='Ringing', call_log=None):
call_log = call_log or get_call_log(call_payload)
if call_log:
call_log.status = status
call_log.to = call_payload.get('DialWhomNumber')
call_log.duration = call_payload.get('DialCallDuration') or 0
call_log.recording_url = call_payload.get('RecordingUrl')
call_log.save(ignore_permissions=True)
@@ -48,7 +51,7 @@ def get_call_log(call_payload):
def create_call_log(call_payload):
call_log = frappe.new_doc('Call Log')
call_log.id = call_payload.get('CallSid')
call_log.to = call_payload.get('CallTo')
call_log.to = call_payload.get('DialWhomNumber')
call_log.medium = call_payload.get('To')
call_log.status = 'Ringing'
setattr(call_log, 'from', call_payload.get('CallFrom'))

View File

@@ -231,8 +231,12 @@ doc_events = {
('Sales Invoice', 'Purchase Invoice', 'Delivery Note'): {
'validate': 'erpnext.regional.india.utils.set_place_of_supply'
},
"Contact":{
"on_trash": "erpnext.support.doctype.issue.issue.update_issue"
"Contact": {
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
},
"Lead": {
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
},
"Email Unsubscribe": {
"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
@@ -279,7 +283,9 @@ scheduler_events = {
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status"
],
"daily_long": [
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
"erpnext.hr.utils.generate_leave_encashment"
],
"monthly_long": [
"erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income",

View File

@@ -4,6 +4,7 @@
"creation": "2013-01-10 16:34:13",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"attendance_details",
"naming_series",
@@ -19,7 +20,9 @@
"department",
"shift",
"attendance_request",
"amended_from"
"amended_from",
"late_entry",
"early_exit"
],
"fields": [
{
@@ -153,12 +156,24 @@
"fieldtype": "Link",
"label": "Shift",
"options": "Shift Type"
},
{
"default": "0",
"fieldname": "late_entry",
"fieldtype": "Check",
"label": "Late Entry"
},
{
"default": "0",
"fieldname": "early_exit",
"fieldtype": "Check",
"label": "Early Exit"
}
],
"icon": "fa fa-ok",
"idx": 1,
"is_submittable": 1,
"modified": "2019-06-05 19:37:30.410071",
"modified": "2019-07-29 20:35:40.845422",
"modified_by": "Administrator",
"module": "HR",
"name": "Attendance",

View File

@@ -76,6 +76,7 @@ class Employee(NestedSet):
if self.user_id:
self.update_user()
self.update_user_permissions()
self.reset_employee_emails_cache()
def update_user_permissions(self):
if not self.create_user_permission: return
@@ -214,6 +215,15 @@ class Employee(NestedSet):
doc.validate_employee_creation()
doc.db_set("employee", self.name)
def reset_employee_emails_cache(self):
prev_doc = self.get_doc_before_save() or {}
cell_number = self.get('cell_number')
prev_number = prev_doc.get('cell_number')
if (cell_number != prev_number or
self.get('user_id') != prev_doc.get('user_id')):
frappe.cache().hdel('employees_with_number', cell_number)
frappe.cache().hdel('employees_with_number', prev_number)
def get_timeline_data(doctype, name):
'''Return timeline for attendance'''
return dict(frappe.db.sql('''select unix_timestamp(attendance_date), count(*)

View File

@@ -64,13 +64,20 @@ class EmployeeAdvance(Document):
def update_claimed_amount(self):
claimed_amount = frappe.db.sql("""
select sum(ifnull(allocated_amount, 0))
from `tabExpense Claim Advance`
where employee_advance = %s and docstatus=1 and allocated_amount > 0
SELECT sum(ifnull(allocated_amount, 0))
FROM `tabExpense Claim Advance` eca, `tabExpense Claim` ec
WHERE
eca.employee_advance = %s
AND ec.approval_status="Approved"
AND ec.name = eca.parent
AND ec.docstatus=1
AND eca.allocated_amount > 0
""", self.name)[0][0] or 0
if claimed_amount:
frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
self.reload()
self.set_status()
frappe.db.set_value("Employee Advance", self.name, "status", self.status)
@frappe.whitelist()
def get_due_advance_amount(employee, posting_date):

View File

@@ -14,8 +14,6 @@
"device_id",
"skip_auto_attendance",
"attendance",
"entry_grace_period_consequence",
"exit_grace_period_consequence",
"shift_start",
"shift_end",
"shift_actual_start",
@@ -80,20 +78,6 @@
"options": "Attendance",
"read_only": 1
},
{
"default": "0",
"fieldname": "entry_grace_period_consequence",
"fieldtype": "Check",
"hidden": 1,
"label": "Entry Grace Period Consequence"
},
{
"default": "0",
"fieldname": "exit_grace_period_consequence",
"fieldtype": "Check",
"hidden": 1,
"label": "Exit Grace Period Consequence"
},
{
"fieldname": "shift_start",
"fieldtype": "Datetime",
@@ -119,7 +103,7 @@
"label": "Shift Actual End"
}
],
"modified": "2019-06-10 15:33:22.731697",
"modified": "2019-07-23 23:47:33.975263",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Checkin",

View File

@@ -72,7 +72,7 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N
return doc
def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, shift=None):
def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, shift=None):
"""Creates an attendance and links the attendance to the Employee Checkin.
Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown.
@@ -98,7 +98,9 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
'status': attendance_status,
'working_hours': working_hours,
'company': employee_doc.company,
'shift': shift
'shift': shift,
'late_entry': late_entry,
'early_exit': early_exit
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
@@ -124,11 +126,16 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
"""
total_hours = 0
in_time = out_time = None
if check_in_out_type == 'Alternating entries as IN and OUT during the same shift':
in_time = logs[0].time
if len(logs) >= 2:
out_time = logs[-1].time
if working_hours_calc_type == 'First Check-in and Last Check-out':
# assumption in this case: First log always taken as IN, Last log always taken as OUT
total_hours = time_diff_in_hours(logs[0].time, logs[-1].time)
total_hours = time_diff_in_hours(in_time, logs[-1].time)
elif working_hours_calc_type == 'Every Valid Check-in and Check-out':
logs = logs[:]
while len(logs) >= 2:
total_hours += time_diff_in_hours(logs[0].time, logs[1].time)
del logs[:2]
@@ -138,11 +145,15 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
first_in_log = logs[find_index_in_dict(logs, 'log_type', 'IN')]
last_out_log = logs[len(logs)-1-find_index_in_dict(reversed(logs), 'log_type', 'OUT')]
if first_in_log and last_out_log:
total_hours = time_diff_in_hours(first_in_log.time, last_out_log.time)
in_time, out_time = first_in_log.time, last_out_log.time
total_hours = time_diff_in_hours(in_time, out_time)
elif working_hours_calc_type == 'Every Valid Check-in and Check-out':
in_log = out_log = None
for log in logs:
if in_log and out_log:
if not in_time:
in_time = in_log.time
out_time = out_log.time
total_hours += time_diff_in_hours(in_log.time, out_log.time)
in_log = out_log = None
if not in_log:
@@ -150,8 +161,9 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
elif not out_log:
out_log = log if log.log_type == 'OUT' else None
if in_log and out_log:
out_time = out_log.time
total_hours += time_diff_in_hours(in_log.time, out_log.time)
return total_hours
return total_hours, in_time, out_time
def time_diff_in_hours(start, end):
return round((end-start).total_seconds() / 3600, 1)

View File

@@ -70,16 +70,16 @@ class TestEmployeeCheckin(unittest.TestCase):
logs_type_2 = [frappe._dict(x) for x in logs_type_2]
working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[0])
self.assertEqual(working_hours, 6.5)
self.assertEqual(working_hours, (6.5, logs_type_1[0].time, logs_type_1[-1].time))
working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[1])
self.assertEqual(working_hours, 4.5)
self.assertEqual(working_hours, (4.5, logs_type_1[0].time, logs_type_1[-1].time))
working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[0])
self.assertEqual(working_hours, 5)
self.assertEqual(working_hours, (5, logs_type_2[1].time, logs_type_2[-1].time))
working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[1])
self.assertEqual(working_hours, 4.5)
self.assertEqual(working_hours, (4.5, logs_type_2[1].time, logs_type_2[-1].time))
def make_n_checkins(employee, n, hours_to_reverse=1):
logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n+1))]

View File

@@ -210,10 +210,42 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "notify_users_by_email",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notify users by email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -548,7 +580,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:55.968224",
"modified": "2019-08-01 16:15:55.968224",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Onboarding",

View File

@@ -29,6 +29,9 @@ class EmployeeOnboarding(EmployeeBoardingController):
def on_submit(self):
super(EmployeeOnboarding, self).on_submit()
def on_update_after_submit(self):
self.create_task_and_notify_user()
def on_cancel(self):
super(EmployeeOnboarding, self).on_cancel()

View File

@@ -145,40 +145,43 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "project",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Project",
"length": 0,
"no_copy": 0,
"options": "Project",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "notify_users_by_email",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notify users by email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@@ -276,7 +279,40 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "project",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Project",
"length": 0,
"no_copy": 0,
"options": "Project",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@@ -550,7 +586,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:39.025898",
"modified": "2019-08-03 16:15:39.025898",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Separation",

View File

@@ -11,6 +11,9 @@ class EmployeeSeparation(EmployeeBoardingController):
def on_submit(self):
super(EmployeeSeparation, self).on_submit()
def on_update_after_submit(self):
self.create_task_and_notify_user()
def on_cancel(self):
super(EmployeeSeparation, self).on_cancel()

View File

@@ -183,7 +183,7 @@ frappe.ui.form.on("Expense Claim", {
refresh: function(frm) {
frm.trigger("toggle_fields");
if(frm.doc.docstatus == 1) {
if(frm.doc.docstatus === 1 && frm.doc.approval_status !== "Rejected") {
frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.route_options = {
voucher_no: frm.doc.name,
@@ -194,7 +194,7 @@ frappe.ui.form.on("Expense Claim", {
}, __("View"));
}
if (frm.doc.docstatus===1
if (frm.doc.docstatus===1 && !cint(frm.doc.is_paid) && cint(frm.doc.grand_total) > 0
&& (cint(frm.doc.total_amount_reimbursed) < cint(frm.doc.total_sanctioned_amount))
&& frappe.model.can_create("Payment Entry")) {
frm.add_custom_button(__('Payment'),

View File

@@ -24,6 +24,7 @@
"column_break_18",
"leave_approver_mandatory_in_leave_application",
"show_leaves_of_all_department_members_in_calendar",
"auto_leave_encashment",
"hiring_settings",
"check_vacancies"
],
@@ -153,12 +154,18 @@
"fieldname": "check_vacancies",
"fieldtype": "Check",
"label": "Check Vacancies On Job Offer Creation"
},
{
"default": "0",
"fieldname": "auto_leave_encashment",
"fieldtype": "Check",
"label": "Auto Leave Encashment"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"modified": "2019-07-01 18:59:55.256878",
"modified": "2019-08-05 13:07:17.993968",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",

View File

@@ -21,11 +21,41 @@ frappe.ui.form.on("Leave Allocation", {
})
},
refresh: function(frm) {
if(frm.doc.docstatus === 1 && frm.doc.expired) {
var valid_expiry = moment(frappe.datetime.get_today()).isBetween(frm.doc.from_date, frm.doc.to_date);
if(valid_expiry) {
// expire current allocation
frm.add_custom_button(__('Expire Allocation'), function() {
frm.trigger("expire_allocation");
});
}
}
},
expire_allocation: function(frm) {
frappe.call({
method: 'erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.expire_allocation',
args: {
'allocation': frm.doc,
'expiry_date': frappe.datetime.get_today()
},
freeze: true,
callback: function(r){
if(!r.exc){
frappe.msgprint(__("Allocation Expired!"));
}
frm.refresh();
}
});
},
employee: function(frm) {
frm.trigger("calculate_total_leaves_allocated");
},
leave_type: function(frm) {
frm.trigger("leave_policy");
frm.trigger("calculate_total_leaves_allocated");
},
@@ -33,37 +63,38 @@ frappe.ui.form.on("Leave Allocation", {
frm.trigger("calculate_total_leaves_allocated");
},
carry_forwarded_leaves: function(frm) {
unused_leaves: function(frm) {
frm.set_value("total_leaves_allocated",
flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated));
flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated));
},
new_leaves_allocated: function(frm) {
frm.set_value("total_leaves_allocated",
flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated));
flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated));
},
leave_policy: function(frm) {
if(frm.doc.leave_policy && frm.doc.leave_type) {
frappe.db.get_value("Leave Policy Detail",{
'parent': frm.doc.leave_policy,
'leave_type': frm.doc.leave_type
}, 'annual_allocation', (r) => {
if (r && !r.exc) frm.set_value("new_leaves_allocated", flt(r.annual_allocation));
}, "Leave Policy");
}
},
calculate_total_leaves_allocated: function(frm) {
if (cint(frm.doc.carry_forward) == 1 && frm.doc.leave_type && frm.doc.employee) {
return frappe.call({
method: "erpnext.hr.doctype.leave_allocation.leave_allocation.get_carry_forwarded_leaves",
args: {
"employee": frm.doc.employee,
"date": frm.doc.from_date,
"leave_type": frm.doc.leave_type,
"carry_forward": frm.doc.carry_forward
},
method: "set_total_leaves_allocated",
doc: frm.doc,
callback: function(r) {
if (!r.exc && r.message) {
frm.set_value('carry_forwarded_leaves', r.message);
frm.set_value("total_leaves_allocated",
flt(r.message) + flt(frm.doc.new_leaves_allocated));
}
frm.refresh_fields();
}
})
} else if (cint(frm.doc.carry_forward) == 0) {
frm.set_value("carry_forwarded_leaves", 0);
frm.set_value("unused_leaves", 0);
frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated));
}
}
})
});

View File

@@ -1,683 +1,220 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "naming_series:",
"beta": 0,
"creation": "2013-02-20 19:10:38",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB",
"field_order": [
"naming_series",
"employee",
"employee_name",
"department",
"column_break1",
"leave_type",
"from_date",
"to_date",
"section_break_6",
"new_leaves_allocated",
"carry_forward",
"unused_leaves",
"total_leaves_allocated",
"total_leaves_encashed",
"column_break_10",
"compensatory_request",
"leave_period",
"leave_policy",
"carry_forwarded_leaves_count",
"expired",
"amended_from",
"notes",
"description"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Series",
"length": 0,
"no_copy": 1,
"options": "HR-LAL-.YYYY.-",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
"set_only_once": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Employee",
"length": 0,
"no_copy": 0,
"oldfieldname": "employee",
"oldfieldtype": "Link",
"options": "Employee",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"search_index": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"search_index": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "leave_type",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Leave Type",
"length": 0,
"no_copy": 0,
"oldfieldname": "leave_type",
"oldfieldtype": "Link",
"options": "Leave Type",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"search_index": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "From Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "To Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allocation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Allocation"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 1,
"collapsible": 0,
"columns": 0,
"fieldname": "new_leaves_allocated",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "New Leaves Allocated",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "New Leaves Allocated"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "",
"default": "0",
"fieldname": "carry_forward",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Add unused leaves from previous allocations",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Add unused leaves from previous allocations"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "carry_forward",
"fieldname": "carry_forwarded_leaves",
"fieldname": "unused_leaves",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Unused leaves",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_leaves_allocated",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Leaves Allocated",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.total_leaves_encashed>0",
"fieldname": "total_leaves_encashed",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Leaves Encashed",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "compensatory_request",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Compensatory Leave Request",
"length": 0,
"no_copy": 0,
"options": "Compensatory Leave Request",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "leave_period",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Leave Period",
"length": 0,
"no_copy": 0,
"options": "Leave Period",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"fetch_from": "employee.leave_policy",
"fieldname": "leave_policy",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Leave Policy",
"options": "Leave Policy",
"read_only": 1
},
{
"default": "0",
"fieldname": "expired",
"fieldtype": "Check",
"hidden": 1,
"in_standard_filter": 1,
"label": "Expired",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"oldfieldname": "amended_from",
"oldfieldtype": "Data",
"options": "Leave Allocation",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "notes",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Notes"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "reason",
"oldfieldtype": "Small Text",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "300px"
},
{
"depends_on": "carry_forwarded_leaves_count",
"fieldname": "carry_forwarded_leaves_count",
"fieldtype": "Float",
"label": "Carry Forwarded Leaves",
"read_only": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-ok",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-01-30 11:28:09.360525",
"modified": "2019-08-08 15:08:42.440909",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Allocation",
@@ -689,15 +226,10 @@
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
@@ -709,28 +241,19 @@
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "employee,employee_name,leave_type,total_leaves_allocated",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "employee",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
"timeline_field": "employee"
}

View File

@@ -3,11 +3,11 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import flt, date_diff, formatdate
from frappe.utils import flt, date_diff, formatdate, add_days, today, getdate
from frappe import _
from frappe.model.document import Document
from erpnext.hr.utils import set_employee_name, get_leave_period
from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation, create_leave_ledger_entry
class OverlapError(frappe.ValidationError): pass
class BackDatedAllocationError(frappe.ValidationError): pass
@@ -40,14 +40,18 @@ class LeaveAllocation(Document):
frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")\
.format(self.leave_type, self.employee))
def on_update_after_submit(self):
self.validate_new_leaves_allocated_value()
self.set_total_leaves_allocated()
def on_submit(self):
self.create_leave_ledger_entry()
frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves))
frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated))
# expire all unused leaves in the ledger on creation of carry forward allocation
allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee)
if self.carry_forward and allocation:
expire_allocation(allocation)
self.validate_against_leave_applications()
def on_cancel(self):
self.create_leave_ledger_entry(submit=False)
if self.carry_forward:
self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True)
def validate_period(self):
if date_diff(self.to_date, self.from_date) <= 0:
@@ -87,13 +91,32 @@ class LeaveAllocation(Document):
BackDatedAllocationError)
def set_total_leaves_allocated(self):
self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee,
self.unused_leaves = get_carry_forwarded_leaves(self.employee,
self.leave_type, self.from_date, self.carry_forward)
self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated)
self.total_leaves_allocated = flt(self.unused_leaves) + flt(self.new_leaves_allocated)
if self.carry_forward:
self.maintain_carry_forwarded_leaves()
self.set_carry_forwarded_leaves_in_previous_allocation()
if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"):
frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}".format(self.leave_type)))
frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}").format(self.leave_type))
def maintain_carry_forwarded_leaves(self):
''' Reduce the carry forwarded leaves to be within the maximum allowed leaves '''
max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed")
if self.new_leaves_allocated <= max_leaves_allowed <= self.total_leaves_allocated:
self.unused_leaves = max_leaves_allowed - flt(self.new_leaves_allocated)
self.total_leaves_allocated = flt(max_leaves_allowed)
def set_carry_forwarded_leaves_in_previous_allocation(self, on_cancel=False):
''' Set carry forwarded leaves in previous allocation '''
previous_allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee)
if on_cancel:
self.unused_leaves = 0.0
frappe.db.set_value("Leave Allocation", previous_allocation.name, 'carry_forwarded_leaves_count', self.unused_leaves)
def validate_total_leaves_allocated(self):
# Adding a day to include To Date in the difference
@@ -101,15 +124,37 @@ class LeaveAllocation(Document):
if date_difference < self.total_leaves_allocated:
frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError)
def validate_against_leave_applications(self):
leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type,
self.from_date, self.to_date)
def create_leave_ledger_entry(self, submit=True):
if self.unused_leaves:
expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carry_forwarded_leaves_after_days")
end_date = add_days(self.from_date, expiry_days - 1) if expiry_days else self.to_date
args = dict(
leaves=self.unused_leaves,
from_date=self.from_date,
to_date= min(getdate(end_date), getdate(self.to_date)),
is_carry_forward=1
)
create_leave_ledger_entry(self, args, submit)
if flt(leaves_taken) > flt(self.total_leaves_allocated):
if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken))
else:
frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError)
args = dict(
leaves=self.new_leaves_allocated,
from_date=self.from_date,
to_date=self.to_date,
is_carry_forward=0
)
create_leave_ledger_entry(self, args, submit)
def get_previous_allocation(from_date, leave_type, employee):
''' Returns document properties of previous allocation '''
return frappe.db.get_value("Leave Allocation",
filters={
'to_date': ("<", from_date),
'leave_type': leave_type,
'employee': employee,
'docstatus': 1
},
order_by='to_date DESC',
fieldname=['name', 'from_date', 'to_date', 'employee', 'leave_type'], as_dict=1)
def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
leave_allocated = 0
@@ -136,25 +181,28 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
@frappe.whitelist()
def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None):
carry_forwarded_leaves = 0
if carry_forward:
''' Returns carry forwarded leaves for the given employee '''
unused_leaves = 0.0
previous_allocation = get_previous_allocation(date, leave_type, employee)
if carry_forward and previous_allocation:
validate_carry_forward(leave_type)
unused_leaves = get_unused_leaves(employee, leave_type, previous_allocation.from_date, previous_allocation.to_date)
previous_allocation = frappe.db.sql("""
select name, from_date, to_date, total_leaves_allocated
from `tabLeave Allocation`
where employee=%s and leave_type=%s and docstatus=1 and to_date < %s
order by to_date desc limit 1
""", (employee, leave_type, date), as_dict=1)
if previous_allocation:
leaves_taken = get_approved_leaves_for_period(employee, leave_type,
previous_allocation[0].from_date, previous_allocation[0].to_date)
return unused_leaves
carry_forwarded_leaves = flt(previous_allocation[0].total_leaves_allocated) - flt(leaves_taken)
return carry_forwarded_leaves
def get_unused_leaves(employee, leave_type, from_date, to_date):
''' Returns unused leaves between the given period while skipping leave allocation expiry '''
leaves = frappe.get_all("Leave Ledger Entry", filters={
'employee': employee,
'leave_type': leave_type,
'from_date': ('>=', from_date),
'to_date': ('<=', to_date)
}, or_filters={
'is_expired': 0,
'is_carry_forward': 1
}, fields=['sum(leaves) as leaves'])
return flt(leaves[0]['leaves'])
def validate_carry_forward(leave_type):
if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"):
frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))

View File

@@ -12,4 +12,9 @@ def get_data():
'items': ['Leave Encashment']
}
],
'reports': [
{
'items': ['Employee Leave Balance']
}
]
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
// render
frappe.listview_settings['Leave Allocation'] = {
get_indicator: function(doc) {
if(doc.status==="Expired") {
return [__("Expired"), "darkgrey", "expired, =, 1"];
}
},
};

View File

@@ -34,7 +34,7 @@ QUnit.test("Test: Leave allocation [HR]", function (assert) {
() => assert.equal(today_date, cur_frm.doc.from_date,
"from date correctly set"),
// check for total leaves
() => assert.equal(cur_frm.doc.carry_forwarded_leaves + 2, cur_frm.doc.total_leaves_allocated,
() => assert.equal(cur_frm.doc.unused_leaves + 2, cur_frm.doc.total_leaves_allocated,
"total leave calculation is correctly set"),
() => done()
]);

View File

@@ -1,12 +1,14 @@
from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils import getdate
from frappe.utils import nowdate, add_months, getdate, add_days
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation
class TestLeaveAllocation(unittest.TestCase):
def test_overlapping_allocation(self):
frappe.db.sql("delete from `tabLeave Allocation`")
employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
leaves = [
{
@@ -18,7 +20,7 @@ class TestLeaveAllocation(unittest.TestCase):
"from_date": getdate("2015-10-01"),
"to_date": getdate("2015-10-31"),
"new_leaves_allocated": 5,
"docstatus": 1
"docstatus": 1
},
{
"doctype": "Leave Allocation",
@@ -28,17 +30,17 @@ class TestLeaveAllocation(unittest.TestCase):
"leave_type": "_Test Leave Type",
"from_date": getdate("2015-09-01"),
"to_date": getdate("2015-11-30"),
"new_leaves_allocated": 5
"new_leaves_allocated": 5
}
]
frappe.get_doc(leaves[0]).save()
self.assertRaises(frappe.ValidationError, frappe.get_doc(leaves[1]).save)
def test_invalid_period(self):
def test_invalid_period(self):
employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
d = frappe.get_doc({
doc = frappe.get_doc({
"doctype": "Leave Allocation",
"__islocal": 1,
"employee": employee.name,
@@ -46,15 +48,15 @@ class TestLeaveAllocation(unittest.TestCase):
"leave_type": "_Test Leave Type",
"from_date": getdate("2015-09-30"),
"to_date": getdate("2015-09-1"),
"new_leaves_allocated": 5
"new_leaves_allocated": 5
})
#invalid period
self.assertRaises(frappe.ValidationError, d.save)
self.assertRaises(frappe.ValidationError, doc.save)
def test_allocated_leave_days_over_period(self):
employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
d = frappe.get_doc({
doc = frappe.get_doc({
"doctype": "Leave Allocation",
"__islocal": 1,
"employee": employee.name,
@@ -62,10 +64,102 @@ class TestLeaveAllocation(unittest.TestCase):
"leave_type": "_Test Leave Type",
"from_date": getdate("2015-09-1"),
"to_date": getdate("2015-09-30"),
"new_leaves_allocated": 35
"new_leaves_allocated": 35
})
#allocated leave more than period
self.assertRaises(frappe.ValidationError, d.save)
#allocated leave more than period
self.assertRaises(frappe.ValidationError, doc.save)
def test_carry_forward_calculation(self):
frappe.db.sql("delete from `tabLeave Allocation`")
frappe.db.sql("delete from `tabLeave Ledger Entry`")
leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1)
leave_type.submit()
# initial leave allocation
leave_allocation = create_leave_allocation(
leave_type="_Test_CF_leave",
from_date=add_months(nowdate(), -12),
to_date=add_months(nowdate(), -1),
carry_forward=0)
leave_allocation.submit()
# leave allocation with carry forward from previous allocation
leave_allocation_1 = create_leave_allocation(
leave_type="_Test_CF_leave",
carry_forward=1)
leave_allocation_1.submit()
self.assertEquals(leave_allocation.total_leaves_allocated, leave_allocation_1.unused_leaves)
def test_carry_forward_leaves_expiry(self):
frappe.db.sql("delete from `tabLeave Allocation`")
frappe.db.sql("delete from `tabLeave Ledger Entry`")
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90)
leave_type.submit()
# initial leave allocation
leave_allocation = create_leave_allocation(
leave_type="_Test_CF_leave_expiry",
from_date=add_months(nowdate(), -24),
to_date=add_months(nowdate(), -12),
carry_forward=0)
leave_allocation.submit()
leave_allocation = create_leave_allocation(
leave_type="_Test_CF_leave_expiry",
from_date=add_days(nowdate(), -90),
to_date=add_days(nowdate(), 100),
carry_forward=1)
leave_allocation.submit()
# expires all the carry forwarded leaves after 90 days
process_expired_allocation()
# leave allocation with carry forward of only new leaves allocated
leave_allocation_1 = create_leave_allocation(
leave_type="_Test_CF_leave_expiry",
carry_forward=1,
from_date=add_months(nowdate(), 6),
to_date=add_months(nowdate(), 12))
leave_allocation_1.submit()
self.assertEquals(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated)
def test_creation_of_leave_ledger_entry_on_submit(self):
frappe.db.sql("delete from `tabLeave Allocation`")
leave_allocation = create_leave_allocation()
leave_allocation.submit()
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name))
self.assertEquals(len(leave_ledger_entry), 1)
self.assertEquals(leave_ledger_entry[0].employee, leave_allocation.employee)
self.assertEquals(leave_ledger_entry[0].leave_type, leave_allocation.leave_type)
self.assertEquals(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated)
# check if leave ledger entry is deleted on cancellation
leave_allocation.cancel()
self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name}))
def create_leave_allocation(**args):
args = frappe._dict(args)
employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
leave_allocation = frappe.get_doc({
"doctype": "Leave Allocation",
"__islocal": 1,
"employee": args.employee or employee.name,
"employee_name": args.employee_name or employee.employee_name,
"leave_type": args.leave_type or "_Test Leave Type",
"from_date": args.from_date or nowdate(),
"new_leaves_allocated": args.new_leaves_created or 15,
"carry_forward": args.carry_forward or 0,
"to_date": args.to_date or add_months(nowdate(), 12)
})
return leave_allocation
test_dependencies = ["Employee", "Leave Type"]

View File

@@ -49,7 +49,7 @@ frappe.ui.form.on("Leave Application", {
async: false,
args: {
employee: frm.doc.employee,
date: frm.doc.posting_date
date: frm.doc.from_date || frm.doc.posting_date
},
callback: function(r) {
if (!r.exc && r.message['leave_allocation']) {
@@ -60,9 +60,8 @@ frappe.ui.form.on("Leave Application", {
}
}
});
$("div").remove(".form-dashboard-section");
let section = frm.dashboard.add_section(
frm.dashboard.add_section(
frappe.render_template('leave_application_dashboard', {
data: leave_details
})
@@ -115,6 +114,7 @@ frappe.ui.form.on("Leave Application", {
},
from_date: function(frm) {
frm.trigger("make_dashboard");
frm.trigger("half_day_datepicker");
frm.trigger("calculate_total_days");
},
@@ -138,12 +138,13 @@ frappe.ui.form.on("Leave Application", {
},
get_leave_balance: function(frm) {
if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date) {
if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date && frm.doc.to_date) {
return frappe.call({
method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on",
args: {
employee: frm.doc.employee,
date: frm.doc.from_date,
to_date: frm.doc.to_date,
leave_type: frm.doc.leave_type,
consider_all_leaves_in_the_allocation_period: true
},

File diff suppressed because it is too large Load Diff

View File

@@ -5,11 +5,12 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
comma_or, get_fullname, add_days, nowdate
comma_or, get_fullname, add_days, nowdate, get_datetime_str
from erpnext.hr.utils import set_employee_name, get_leave_period
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
class LeaveDayBlockedError(frappe.ValidationError): pass
class OverlapError(frappe.ValidationError): pass
@@ -50,6 +51,7 @@ class LeaveApplication(Document):
# notify leave applier about approval
self.notify_employee()
self.create_leave_ledger_entry()
self.reload()
def on_cancel(self):
@@ -57,6 +59,7 @@ class LeaveApplication(Document):
# notify leave applier about cancellation
self.notify_employee()
self.cancel_attendance()
self.create_leave_ledger_entry(submit=False)
def validate_applicable_after(self):
if self.leave_type:
@@ -193,9 +196,9 @@ class LeaveApplication(Document):
frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave."))
if not is_lwp(self.leave_type):
self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, docname=self.name,
self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, self.to_date,
consider_all_leaves_in_the_allocation_period=True)
if self.status != "Rejected" and self.leave_balance < self.total_leave_days:
if self.status != "Rejected" and (self.leave_balance < self.total_leave_days or not self.leave_balance):
if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}")
.format(self.leave_type))
@@ -347,6 +350,54 @@ class LeaveApplication(Document):
except frappe.OutgoingEmailError:
pass
def create_leave_ledger_entry(self, submit=True):
expiry_date = get_allocation_expiry(self.employee, self.leave_type,
self.to_date, self.from_date)
lwp = frappe.db.get_value("Leave Type", self.leave_type, "is_lwp")
if expiry_date:
self.create_ledger_entry_for_intermediate_allocation_expiry(expiry_date, submit, lwp)
else:
args = dict(
leaves=self.total_leave_days * -1,
from_date=self.from_date,
to_date=self.to_date,
is_lwp=lwp
)
create_leave_ledger_entry(self, args, submit)
def create_ledger_entry_for_intermediate_allocation_expiry(self, expiry_date, submit, lwp):
''' splits leave application into two ledger entries to consider expiry of allocation '''
args = dict(
from_date=self.from_date,
to_date=expiry_date,
leaves=(date_diff(expiry_date, self.from_date) + 1) * -1,
is_lwp=lwp
)
create_leave_ledger_entry(self, args, submit)
if getdate(expiry_date) != getdate(self.to_date):
start_date = add_days(expiry_date, 1)
args.update(dict(
from_date=start_date,
to_date=self.to_date,
leaves=date_diff(self.to_date, expiry_date) * -1
))
create_leave_ledger_entry(self, args, submit)
def get_allocation_expiry(employee, leave_type, to_date, from_date):
''' Returns expiry of carry forward allocation in leave ledger entry '''
expiry = frappe.get_all("Leave Ledger Entry",
filters={
'employee': employee,
'leave_type': leave_type,
'is_carry_forward': 1,
'transaction_type': 'Leave Allocation',
'to_date': ['between', (from_date, to_date)]
},fields=['to_date'])
return expiry[0]['to_date'] if expiry else None
@frappe.whitelist()
def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None):
number_of_days = 0
@@ -364,14 +415,16 @@ def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day
@frappe.whitelist()
def get_leave_details(employee, date):
allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict())
allocation_records = get_leave_allocation_records(employee, date)
leave_allocation = {}
for d in allocation_records:
allocation = allocation_records.get(d, frappe._dict())
date = allocation.to_date
leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, date, status="Approved")
leaves_pending = get_leaves_for_period(employee, d, allocation.from_date, date, status="Open")
remaining_leaves = allocation.total_leaves_allocated - leaves_taken - leaves_pending
remaining_leaves = get_leave_balance_on(employee, d, date, to_date = allocation.to_date,
consider_all_leaves_in_the_allocation_period=True)
end_date = allocation.to_date
leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, end_date) * -1
leaves_pending = get_pending_leaves_for_period(employee, d, allocation.from_date, end_date)
leave_allocation[d] = {
"total_leaves": allocation.total_leaves_allocated,
"leaves_taken": leaves_taken,
@@ -386,27 +439,131 @@ def get_leave_details(employee, date):
return ret
@frappe.whitelist()
def get_leave_balance_on(employee, leave_type, date, allocation_records=None, docname=None,
consider_all_leaves_in_the_allocation_period=False, consider_encashed_leaves=True):
def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), consider_all_leaves_in_the_allocation_period=False):
'''
Returns leave balance till date
:param employee: employee name
:param leave_type: leave type
:param date: date to check balance on
:param to_date: future date to check for allocation expiry
:param consider_all_leaves_in_the_allocation_period: consider all leaves taken till the allocation end date
'''
if allocation_records == None:
allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict())
allocation_records = get_leave_allocation_records(employee, date, leave_type)
allocation = allocation_records.get(leave_type, frappe._dict())
if consider_all_leaves_in_the_allocation_period:
date = allocation.to_date
leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, date, status="Approved", docname=docname)
leaves_encashed = 0
if frappe.db.get_value("Leave Type", leave_type, 'allow_encashment') and consider_encashed_leaves:
leaves_encashed = flt(allocation.total_leaves_encashed)
return flt(allocation.total_leaves_allocated) - (flt(leaves_taken) + flt(leaves_encashed))
end_date = allocation.to_date if consider_all_leaves_in_the_allocation_period else date
expiry = get_allocation_expiry(employee, leave_type, to_date, date)
def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docname=None):
leave_applications = frappe.db.sql("""
select name, employee, leave_type, from_date, to_date, total_leave_days
from `tabLeave Application`
leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, end_date)
return get_remaining_leaves(allocation, leaves_taken, date, expiry)
def get_leave_allocation_records(employee, date, leave_type=None):
''' returns the total allocated leaves and carry forwarded leaves based on ledger entries '''
conditions = ("and leave_type='%s'" % leave_type) if leave_type else ""
allocation_details = frappe.db.sql("""
SELECT
SUM(CASE WHEN is_carry_forward = 1 THEN leaves ELSE 0 END) as cf_leaves,
SUM(CASE WHEN is_carry_forward = 0 THEN leaves ELSE 0 END) as new_leaves,
MIN(from_date) as from_date,
MAX(to_date) as to_date,
leave_type
FROM `tabLeave Ledger Entry`
WHERE
from_date <= %(date)s
AND to_date >= %(date)s
AND docstatus=1
AND transaction_type="Leave Allocation"
AND employee=%(employee)s
AND is_expired=0
AND is_lwp=0
{0}
GROUP BY employee, leave_type
""".format(conditions), dict(date=date, employee=employee), as_dict=1) #nosec
allocated_leaves = frappe._dict()
for d in allocation_details:
allocated_leaves.setdefault(d.leave_type, frappe._dict({
"from_date": d.from_date,
"to_date": d.to_date,
"total_leaves_allocated": flt(d.cf_leaves) + flt(d.new_leaves),
"unused_leaves": d.cf_leaves,
"new_leaves_allocated": d.new_leaves,
"leave_type": d.leave_type
}))
return allocated_leaves
def get_pending_leaves_for_period(employee, leave_type, from_date, to_date):
''' Returns leaves that are pending approval '''
return frappe.db.get_value("Leave Application",
filters={
"employee": employee,
"leave_type": leave_type,
"from_date": ("<=", from_date),
"to_date": (">=", to_date),
"status": "Open"
}, fieldname=['SUM(total_leave_days)']) or flt(0)
def get_remaining_leaves(allocation, leaves_taken, date, expiry):
''' Returns minimum leaves remaining after comparing with remaining days for allocation expiry '''
def _get_remaining_leaves(allocated_leaves, end_date):
remaining_leaves = flt(allocated_leaves) + flt(leaves_taken)
if remaining_leaves > 0:
remaining_days = date_diff(end_date, date) + 1
remaining_leaves = min(remaining_days, remaining_leaves)
return remaining_leaves
total_leaves = allocation.total_leaves_allocated
if expiry and allocation.unused_leaves:
remaining_leaves = _get_remaining_leaves(allocation.unused_leaves, expiry)
total_leaves = flt(allocation.new_leaves_allocated) + flt(remaining_leaves)
return _get_remaining_leaves(total_leaves, allocation.to_date)
def get_leaves_for_period(employee, leave_type, from_date, to_date):
leave_entries = get_leave_entries(employee, leave_type, from_date, to_date)
leave_days = 0
for leave_entry in leave_entries:
inclusive_period = leave_entry.from_date >= getdate(from_date) and leave_entry.to_date <= getdate(to_date)
if inclusive_period and leave_entry.transaction_type == 'Leave Encashment':
leave_days += leave_entry.leaves
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \
and not skip_expiry_leaves(leave_entry, to_date):
leave_days += leave_entry.leaves
else:
if leave_entry.from_date < getdate(from_date):
leave_entry.from_date = from_date
if leave_entry.to_date > getdate(to_date):
leave_entry.to_date = to_date
leave_days += get_number_of_leave_days(employee, leave_type,
leave_entry.from_date, leave_entry.to_date) * -1
return leave_days
def skip_expiry_leaves(leave_entry, date):
''' Checks whether the expired leaves coincide with the to_date of leave balance check '''
end_date = frappe.db.get_value("Leave Allocation", {'name': leave_entry.transaction_name}, ['to_date'])
return True if end_date == date and not leave_entry.is_carry_forward else False
def get_leave_entries(employee, leave_type, from_date, to_date):
''' Returns leave entries between from_date and to_date '''
return frappe.db.sql("""
select employee, leave_type, from_date, to_date, leaves, transaction_type, is_carry_forward
from `tabLeave Ledger Entry`
where employee=%(employee)s and leave_type=%(leave_type)s
and status = %(status)s and docstatus != 2
and docstatus=1
and leaves<0
and (from_date between %(from_date)s and %(to_date)s
or to_date between %(from_date)s and %(to_date)s
or (from_date < %(from_date)s and to_date > %(to_date)s))
@@ -414,43 +571,8 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docn
"from_date": from_date,
"to_date": to_date,
"employee": employee,
"status": status,
"leave_type": leave_type
}, as_dict=1)
leave_days = 0
for leave_app in leave_applications:
if docname and leave_app.name == docname:
continue
if leave_app.from_date >= getdate(from_date) and leave_app.to_date <= getdate(to_date):
leave_days += leave_app.total_leave_days
else:
if leave_app.from_date < getdate(from_date):
leave_app.from_date = from_date
if leave_app.to_date > getdate(to_date):
leave_app.to_date = to_date
leave_days += get_number_of_leave_days(employee, leave_type,
leave_app.from_date, leave_app.to_date)
return leave_days
def get_leave_allocation_records(date, employee=None):
conditions = (" and employee='%s'" % employee) if employee else ""
leave_allocation_records = frappe.db.sql("""
select employee, leave_type, total_leaves_allocated, total_leaves_encashed, from_date, to_date
from `tabLeave Allocation`
where %s between from_date and to_date and docstatus=1 {0}""".format(conditions), (date), as_dict=1)
allocated_leaves = frappe._dict()
for d in leave_allocation_records:
allocated_leaves.setdefault(d.employee, frappe._dict()).setdefault(d.leave_type, frappe._dict({
"from_date": d.from_date,
"to_date": d.to_date,
"total_leaves_allocated": d.total_leaves_allocated,
"total_leaves_encashed":d.total_leaves_encashed
}))
return allocated_leaves
@frappe.whitelist()
def get_holidays(employee, from_date, to_date):
@@ -629,4 +751,4 @@ def get_leave_approver(employee, department=None):
if department:
return frappe.db.get_value('Department Approver', {'parent': department,
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')

View File

@@ -0,0 +1,14 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'reports': [
{
'label': _('Reports'),
'items': ['Employee Leave Balance']
}
]
}

View File

@@ -7,7 +7,9 @@ import unittest
from erpnext.hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError, NotAnOptionalHoliday, get_leave_balance_on
from frappe.permissions import clear_user_permissions_for_doctype
from frappe.utils import add_days, nowdate, now_datetime, getdate
from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
test_dependencies = ["Leave Allocation", "Leave Block List"]
@@ -17,6 +19,7 @@ _test_records = [
"doctype": "Leave Application",
"employee": "_T-Employee-00001",
"from_date": "2013-05-01",
"description": "_Test Reason",
"leave_type": "_Test Leave Type",
"posting_date": "2013-01-02",
"to_date": "2013-05-05"
@@ -26,6 +29,7 @@ _test_records = [
"doctype": "Leave Application",
"employee": "_T-Employee-00002",
"from_date": "2013-05-01",
"description": "_Test Reason",
"leave_type": "_Test Leave Type",
"posting_date": "2013-01-02",
"to_date": "2013-05-05"
@@ -35,6 +39,7 @@ _test_records = [
"doctype": "Leave Application",
"employee": "_T-Employee-00001",
"from_date": "2013-01-15",
"description": "_Test Reason",
"leave_type": "_Test Leave Type LWP",
"posting_date": "2013-01-02",
"to_date": "2013-01-15"
@@ -44,8 +49,8 @@ _test_records = [
class TestLeaveApplication(unittest.TestCase):
def setUp(self):
for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]:
frappe.db.sql("delete from `tab%s`" % dt)
for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Leave Ledger Entry"]:
frappe.db.sql("DELETE FROM `tab%s`" % dt) #nosec
@classmethod
def setUpClass(cls):
@@ -268,13 +273,14 @@ class TestLeaveApplication(unittest.TestCase):
doctype = 'Leave Application',
employee = employee.name,
company = '_Test Company',
description = "_Test Reason",
leave_type = leave_type,
from_date = date,
to_date = date,
))
# can only apply on optional holidays
self.assertTrue(NotAnOptionalHoliday, leave_application.insert)
self.assertRaises(NotAnOptionalHoliday, leave_application.insert)
leave_application.from_date = today
leave_application.to_date = today
@@ -285,7 +291,6 @@ class TestLeaveApplication(unittest.TestCase):
# check leave balance is reduced
self.assertEqual(get_leave_balance_on(employee.name, leave_type, today), 9)
def test_leaves_allowed(self):
employee = get_employee()
leave_period = get_leave_period()
@@ -301,24 +306,25 @@ class TestLeaveApplication(unittest.TestCase):
allocate_leaves(employee, leave_period, leave_type.name, 5)
leave_application = frappe.get_doc(dict(
doctype = 'Leave Application',
doctype = 'Leave Application',
employee = employee.name,
leave_type = leave_type.name,
description = "_Test Reason",
from_date = date,
to_date = add_days(date, 2),
company = "_Test Company",
docstatus = 1,
status = "Approved"
))
self.assertTrue(leave_application.insert())
leave_application.submit()
leave_application = frappe.get_doc(dict(
doctype = 'Leave Application',
employee = employee.name,
leave_type = leave_type.name,
description = "_Test Reason",
from_date = add_days(date, 4),
to_date = add_days(date, 7),
to_date = add_days(date, 8),
company = "_Test Company",
docstatus = 1,
status = "Approved"
@@ -342,6 +348,7 @@ class TestLeaveApplication(unittest.TestCase):
doctype = 'Leave Application',
employee = employee.name,
leave_type = leave_type.name,
description = "_Test Reason",
from_date = date,
to_date = add_days(date, 4),
company = "_Test Company",
@@ -363,6 +370,7 @@ class TestLeaveApplication(unittest.TestCase):
doctype = 'Leave Application',
employee = employee.name,
leave_type = leave_type_1.name,
description = "_Test Reason",
from_date = date,
to_date = add_days(date, 4),
company = "_Test Company",
@@ -392,6 +400,7 @@ class TestLeaveApplication(unittest.TestCase):
doctype = 'Leave Application',
employee = employee.name,
leave_type = leave_type.name,
description = "_Test Reason",
from_date = date,
to_date = add_days(date, 4),
company = "_Test Company",
@@ -401,6 +410,18 @@ class TestLeaveApplication(unittest.TestCase):
self.assertRaises(frappe.ValidationError, leave_application.insert)
def test_leave_balance_near_allocaton_expiry(self):
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90)
leave_type.submit()
create_carry_forwarded_allocation(employee, leave_type)
self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8)), 21)
def test_earned_leave(self):
leave_period = get_leave_period()
employee = get_employee()
@@ -444,9 +465,10 @@ class TestLeaveApplication(unittest.TestCase):
allocation.insert(ignore_permissions=True)
allocation.submit()
leave_application = frappe.get_doc(dict(
doctype = 'Leave Application',
doctype = 'Leave Application',
employee = employee.name,
leave_type = leave_type,
description = "_Test Reason",
from_date = '2018-10-02',
to_date = '2018-10-02',
company = '_Test Company',
@@ -457,9 +479,103 @@ class TestLeaveApplication(unittest.TestCase):
leave_application.submit()
self.assertEqual(leave_application.docstatus, 1)
def make_allocation_record(employee=None, leave_type=None):
frappe.db.sql("delete from `tabLeave Allocation`")
def test_creation_of_leave_ledger_entry_on_submit(self):
employee = get_employee()
leave_type = create_leave_type(leave_type_name = 'Test Leave Type 1')
leave_type.save()
leave_allocation = create_leave_allocation(employee=employee.name, employee_name=employee.employee_name,
leave_type=leave_type.name)
leave_allocation.submit()
leave_application = frappe.get_doc(dict(
doctype = 'Leave Application',
employee = employee.name,
leave_type = leave_type.name,
from_date = add_days(nowdate(), 1),
to_date = add_days(nowdate(), 4),
description = "_Test Reason",
company = "_Test Company",
docstatus = 1,
status = "Approved"
))
leave_application.submit()
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name))
self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee)
self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type)
self.assertEquals(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1)
# check if leave ledger entry is deleted on cancellation
leave_application.cancel()
self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_application.name}))
def test_ledger_entry_creation_on_intermediate_allocation_expiry(self):
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90)
leave_type.submit()
create_carry_forwarded_allocation(employee, leave_type)
leave_application = frappe.get_doc(dict(
doctype = 'Leave Application',
employee = employee.name,
leave_type = leave_type.name,
from_date = add_days(nowdate(), -3),
to_date = add_days(nowdate(), 7),
description = "_Test Reason",
company = "_Test Company",
docstatus = 1,
status = "Approved"
))
leave_application.submit()
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name))
self.assertEquals(len(leave_ledger_entry), 2)
self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee)
self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type)
self.assertEquals(leave_ledger_entry[0].leaves, -9)
self.assertEquals(leave_ledger_entry[1].leaves, -2)
def test_leave_application_creation_after_expiry(self):
# test leave balance for carry forwarded allocation
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
expire_carry_forwarded_leaves_after_days=90)
leave_type.submit()
create_carry_forwarded_allocation(employee, leave_type)
self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
def create_carry_forwarded_allocation(employee, leave_type):
# initial leave allocation
leave_allocation = create_leave_allocation(
leave_type="_Test_CF_leave_expiry",
employee=employee.name,
employee_name=employee.employee_name,
from_date=add_months(nowdate(), -24),
to_date=add_months(nowdate(), -12),
carry_forward=0)
leave_allocation.submit()
leave_allocation = create_leave_allocation(
leave_type="_Test_CF_leave_expiry",
employee=employee.name,
employee_name=employee.employee_name,
from_date=add_days(nowdate(), -84),
to_date=add_days(nowdate(), 100),
carry_forward=1)
leave_allocation.submit()
def make_allocation_record(employee=None, leave_type=None):
allocation = frappe.get_doc({
"doctype": "Leave Allocation",
"employee": employee or "_T-Employee-00001",
@@ -513,4 +629,4 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el
"docstatus": 1
}).insert()
allocate_leave.submit()
allocate_leave.submit()

View File

@@ -10,6 +10,8 @@ from frappe.utils import getdate, nowdate, flt
from erpnext.hr.utils import set_employee_name
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
class LeaveEncashment(Document):
def validate(self):
@@ -25,7 +27,7 @@ class LeaveEncashment(Document):
def on_submit(self):
if not self.leave_allocation:
self.leave_allocation = self.get_leave_allocation()
self.leave_allocation = self.get_leave_allocation().get('name')
additional_salary = frappe.new_doc("Additional Salary")
additional_salary.company = frappe.get_value("Employee", self.employee, "company")
additional_salary.employee = self.employee
@@ -40,6 +42,8 @@ class LeaveEncashment(Document):
frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') + self.encashable_days)
self.create_leave_ledger_entry()
def on_cancel(self):
if self.additional_salary:
frappe.get_doc("Additional Salary", self.additional_salary).cancel()
@@ -48,6 +52,7 @@ class LeaveEncashment(Document):
if self.leave_allocation:
frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') - self.encashable_days)
self.create_leave_ledger_entry(submit=False)
def get_leave_details_for_encashment(self):
salary_structure = get_assigned_salary_structure(self.employee, self.encashment_date or getdate(nowdate()))
@@ -57,8 +62,10 @@ class LeaveEncashment(Document):
if not frappe.db.get_value("Leave Type", self.leave_type, 'allow_encashment'):
frappe.throw(_("Leave Type {0} is not encashable").format(self.leave_type))
self.leave_balance = get_leave_balance_on(self.employee, self.leave_type,
self.encashment_date or getdate(nowdate()), consider_all_leaves_in_the_allocation_period=True)
allocation = self.get_leave_allocation()
self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\
- get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date)
encashable_days = self.leave_balance - frappe.db.get_value('Leave Type', self.leave_type, 'encashment_threshold_days')
self.encashable_days = encashable_days if encashable_days > 0 else 0
@@ -66,12 +73,47 @@ class LeaveEncashment(Document):
per_day_encashment = frappe.db.get_value('Salary Structure', salary_structure , 'leave_encashment_amount_per_day')
self.encashment_amount = self.encashable_days * per_day_encashment if per_day_encashment > 0 else 0
self.leave_allocation = self.get_leave_allocation()
self.leave_allocation = allocation.name
return True
def get_leave_allocation(self):
leave_allocation = frappe.db.sql("""select name from `tabLeave Allocation` where '{0}'
leave_allocation = frappe.db.sql("""select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}'
between from_date and to_date and docstatus=1 and leave_type='{1}'
and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee))
and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) #nosec
return leave_allocation[0][0] if leave_allocation else None
return leave_allocation[0] if leave_allocation else None
def create_leave_ledger_entry(self, submit=True):
args = frappe._dict(
leaves=self.encashable_days * -1,
from_date=self.encashment_date,
to_date=self.encashment_date,
is_carry_forward=0
)
create_leave_ledger_entry(self, args, submit)
# create reverse entry for expired leaves
to_date = self.get_leave_allocation().get('to_date')
if to_date < getdate(nowdate()):
args = frappe._dict(
leaves=self.encashable_days,
from_date=to_date,
to_date=to_date,
is_carry_forward=0
)
create_leave_ledger_entry(self, args, submit)
def create_leave_encashment(leave_allocation):
''' Creates leave encashment for the given allocations '''
for allocation in leave_allocation:
if not get_assigned_salary_structure(allocation.employee, allocation.to_date):
continue
leave_encashment = frappe.get_doc(dict(
doctype="Leave Encashment",
leave_period=allocation.leave_period,
employee=allocation.employee,
leave_type=allocation.leave_type,
encashment_date=allocation.to_date
))
leave_encashment.insert(ignore_permissions=True)

View File

@@ -9,42 +9,43 @@ from frappe.utils import today, add_months
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure
from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy\
test_dependencies = ["Leave Type"]
class TestLeaveEncashment(unittest.TestCase):
def setUp(self):
frappe.db.sql('''delete from `tabLeave Period`''')
def test_leave_balance_value_and_amount(self):
employee = "test_employee_encashment@salary.com"
leave_type = "_Test Leave Type Encashment"
frappe.db.sql('''delete from `tabLeave Allocation`''')
frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
frappe.db.sql('''delete from `tabAdditional Salary`''')
# create the leave policy
leave_policy = frappe.get_doc({
"doctype": "Leave Policy",
"leave_policy_details": [{
"leave_type": leave_type,
"annual_allocation": 10
}]
}).insert()
leave_policy = create_leave_policy(
leave_type="_Test Leave Type Encashment",
annual_allocation=10)
leave_policy.submit()
# create employee, salary structure and assignment
employee = make_employee(employee)
frappe.db.set_value("Employee", employee, "leave_policy", leave_policy.name)
salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", employee,
self.employee = make_employee("test_employee_encashment@example.com")
frappe.db.set_value("Employee", self.employee, "leave_policy", leave_policy.name)
salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
other_details={"leave_encashment_amount_per_day": 50})
# create the leave period and assign the leaves
leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
leave_period.grant_leave_allocation(employee=employee)
self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
self.leave_period.grant_leave_allocation(employee=self.employee)
def test_leave_balance_value_and_amount(self):
frappe.db.sql('''delete from `tabLeave Encashment`''')
leave_encashment = frappe.get_doc(dict(
doctype = 'Leave Encashment',
employee = employee,
leave_type = leave_type,
leave_period = leave_period.name,
payroll_date = today()
doctype='Leave Encashment',
employee=self.employee,
leave_type="_Test Leave Type Encashment",
leave_period=self.leave_period.name,
payroll_date=today()
)).insert()
self.assertEqual(leave_encashment.leave_balance, 10)
@@ -53,3 +54,26 @@ class TestLeaveEncashment(unittest.TestCase):
leave_encashment.submit()
self.assertTrue(frappe.db.get_value("Leave Encashment", leave_encashment.name, "additional_salary"))
def test_creation_of_leave_ledger_entry_on_submit(self):
frappe.db.sql('''delete from `tabLeave Encashment`''')
leave_encashment = frappe.get_doc(dict(
doctype='Leave Encashment',
employee=self.employee,
leave_type="_Test Leave Type Encashment",
leave_period=self.leave_period.name,
payroll_date=today()
)).insert()
leave_encashment.submit()
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name))
self.assertEquals(len(leave_ledger_entry), 1)
self.assertEquals(leave_ledger_entry[0].employee, leave_encashment.employee)
self.assertEquals(leave_ledger_entry[0].leave_type, leave_encashment.leave_type)
self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1)
# check if leave ledger entry is deleted on cancellation
leave_encashment.cancel()
self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_encashment.name}))

View File

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

View File

@@ -0,0 +1,169 @@
{
"creation": "2019-05-09 15:47:39.760406",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"employee",
"employee_name",
"leave_type",
"transaction_type",
"transaction_name",
"leaves",
"column_break_7",
"from_date",
"to_date",
"is_carry_forward",
"is_expired",
"is_lwp",
"amended_from"
],
"fields": [
{
"fieldname": "employee",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Employee",
"options": "Employee"
},
{
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"label": "Employee Name"
},
{
"fieldname": "leave_type",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Leave Type",
"options": "Leave Type"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Leave Ledger Entry",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "transaction_type",
"fieldtype": "Link",
"label": "Transaction Type",
"options": "DocType"
},
{
"fieldname": "transaction_name",
"fieldtype": "Dynamic Link",
"label": "Transaction Name",
"options": "transaction_type"
},
{
"fieldname": "leaves",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Leaves"
},
{
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date"
},
{
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date"
},
{
"default": "0",
"fieldname": "is_carry_forward",
"fieldtype": "Check",
"label": "Is Carry Forward"
},
{
"default": "0",
"fieldname": "is_expired",
"fieldtype": "Check",
"label": "Is Expired"
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "is_lwp",
"fieldtype": "Check",
"label": "Is Leave Without Pay"
}
],
"in_create": 1,
"is_submittable": 1,
"modified": "2019-08-20 14:40:04.130799",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Ledger Entry",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"submit": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "ASC",
"title_field": "employee"
}

View File

@@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
from frappe.utils import add_days, today, flt, DATE_FORMAT, getdate
class LeaveLedgerEntry(Document):
def validate(self):
if getdate(self.from_date) > getdate(self.to_date):
frappe.throw(_("To date needs to be before from date"))
def on_cancel(self):
# allow cancellation of expiry leaves
if self.is_expired:
frappe.db.set_value("Leave Allocation", self.transaction_name, "expired", 0)
else:
frappe.throw(_("Only expired allocation can be cancelled"))
def validate_leave_allocation_against_leave_application(ledger):
''' Checks that leave allocation has no leave application against it '''
leave_application_records = frappe.db.sql_list("""
SELECT transaction_name
FROM `tabLeave Ledger Entry`
WHERE
employee=%s
AND leave_type=%s
AND transaction_type='Leave Application'
AND from_date>=%s
AND to_date<=%s
""", (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date))
if leave_application_records:
frappe.throw(_("Leave allocation %s is linked with leave application %s"
% (ledger.transaction_name, ', '.join(leave_application_records))))
def create_leave_ledger_entry(ref_doc, args, submit=True):
ledger = frappe._dict(
doctype='Leave Ledger Entry',
employee=ref_doc.employee,
employee_name=ref_doc.employee_name,
leave_type=ref_doc.leave_type,
transaction_type=ref_doc.doctype,
transaction_name=ref_doc.name,
is_carry_forward=0,
is_expired=0,
is_lwp=0
)
ledger.update(args)
if submit:
frappe.get_doc(ledger).submit()
else:
delete_ledger_entry(ledger)
def delete_ledger_entry(ledger):
''' Delete ledger entry on cancel of leave application/allocation/encashment '''
if ledger.transaction_type == "Leave Allocation":
validate_leave_allocation_against_leave_application(ledger)
expired_entry = get_previous_expiry_ledger_entry(ledger)
frappe.db.sql("""DELETE
FROM `tabLeave Ledger Entry`
WHERE
`transaction_name`=%s
OR `name`=%s""", (ledger.transaction_name, expired_entry))
def get_previous_expiry_ledger_entry(ledger):
''' Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled '''
creation_date = frappe.db.get_value("Leave Ledger Entry", filters={
'transaction_name': ledger.transaction_name,
'is_expired': 0,
'transaction_type': 'Leave Allocation'
}, fieldname=['creation'])
creation_date = creation_date.strftime(DATE_FORMAT) if creation_date else ''
return frappe.db.get_value("Leave Ledger Entry", filters={
'creation': ('like', creation_date+"%"),
'employee': ledger.employee,
'leave_type': ledger.leave_type,
'is_expired': 1,
'docstatus': 1,
'is_carry_forward': 0
}, fieldname=['name'])
def process_expired_allocation():
''' Check if a carry forwarded allocation has expired and create a expiry ledger entry '''
# fetch leave type records that has carry forwarded leaves expiry
leave_type_records = frappe.db.get_values("Leave Type", filters={
'expire_carry_forwarded_leaves_after_days': (">", 0)
}, fieldname=['name'])
if leave_type_records:
leave_type = [record[0] for record in leave_type_records]
expired_allocation = frappe.db.sql_list("""SELECT name
FROM `tabLeave Ledger Entry`
WHERE
`transaction_type`='Leave Allocation'
AND `is_expired`=1""")
expire_allocation = frappe.get_all("Leave Ledger Entry",
fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
filters={
'to_date': ("<", today()),
'transaction_type': 'Leave Allocation',
'transaction_name': ('not in', expired_allocation)
},
or_filters={
'is_carry_forward': 0,
'leave_type': ('in', leave_type)
})
if expire_allocation:
create_expiry_ledger_entry(expire_allocation)
def create_expiry_ledger_entry(allocations):
''' Create ledger entry for expired allocation '''
for allocation in allocations:
if allocation.is_carry_forward:
expire_carried_forward_allocation(allocation)
else:
expire_allocation(allocation)
def get_remaining_leaves(allocation):
''' Returns remaining leaves from the given allocation '''
return frappe.db.get_value("Leave Ledger Entry",
filters={
'employee': allocation.employee,
'leave_type': allocation.leave_type,
'to_date': ('<=', allocation.to_date),
}, fieldname=['SUM(leaves)'])
@frappe.whitelist()
def expire_allocation(allocation, expiry_date=None):
''' expires non-carry forwarded allocation '''
leaves = get_remaining_leaves(allocation)
expiry_date = expiry_date if expiry_date else allocation.to_date
if leaves:
args = dict(
leaves=flt(leaves) * -1,
transaction_name=allocation.name,
transaction_type='Leave Allocation',
from_date=expiry_date,
to_date=expiry_date,
is_carry_forward=0,
is_expired=1
)
create_leave_ledger_entry(allocation, args)
frappe.db.set_value("Leave Allocation", allocation.name, "expired", 1)
def expire_carried_forward_allocation(allocation):
''' Expires remaining leaves in the on carried forward allocation '''
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date)
leaves = flt(allocation.leaves) + flt(leaves_taken)
if leaves > 0:
args = frappe._dict(
transaction_name=allocation.name,
transaction_type="Leave Allocation",
leaves=allocation.leaves * -1,
is_carry_forward=allocation.is_carry_forward,
is_expired=1,
from_date=allocation.to_date,
to_date=allocation.to_date
)
create_leave_ledger_entry(allocation, args)

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestLeaveLedgerEntry(unittest.TestCase):
pass

View File

@@ -68,7 +68,7 @@ frappe.ui.form.on('Leave Period', {
},
{
"label": "Add unused leaves from previous allocations",
"fieldname": "carry_forward_leaves",
"fieldname": "carry_forward",
"fieldtype": "Check"
}
],

View File

@@ -1,294 +1,294 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "HR-LPR-.YYYY.-.#####",
"beta": 0,
"creation": "2018-04-13 15:20:52.864288",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "HR-LPR-.YYYY.-.#####",
"beta": 0,
"creation": "2018-04-13 15:20:52.864288",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "From Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "From Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "To Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "To Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_active",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Active",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_active",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Active",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "optional_holiday_list",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Holiday List for Optional Leave",
"length": 0,
"no_copy": 0,
"options": "Holiday List",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "optional_holiday_list",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Holiday List for Optional Leave",
"length": 0,
"no_copy": 0,
"options": "Holiday List",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:43.305502",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Period",
"name_case": "",
"owner": "Administrator",
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-05-30 16:15:43.305502",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Period",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@@ -5,9 +5,10 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import getdate, cstr
from frappe.utils import getdate, cstr, add_days, date_diff, getdate, ceil
from frappe.model.document import Document
from erpnext.hr.utils import validate_overlap, get_employee_leave_policy
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_carry_forwarded_leaves
from frappe.utils.background_jobs import enqueue
from six import iteritems
@@ -21,8 +22,8 @@ class LeavePeriod(Document):
condition_str = " and " + " and ".join(conditions) if len(conditions) else ""
employees = frappe.db.sql_list("select name from tabEmployee where status='Active' {condition}"
.format(condition=condition_str), tuple(values))
employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" #nosec
.format(condition=condition_str), tuple(values)))
return employees
@@ -36,29 +37,29 @@ class LeavePeriod(Document):
def grant_leave_allocation(self, grade=None, department=None, designation=None,
employee=None, carry_forward_leaves=0):
employees = self.get_employees({
employee=None, carry_forward=0):
employee_records = self.get_employees({
"grade": grade,
"department": department,
"designation": designation,
"department": department,
"designation": designation,
"name": employee
})
if employees:
if len(employees) > 20:
if employee_records:
if len(employee_records) > 20:
frappe.enqueue(grant_leave_alloc_for_employees, timeout=600,
employees=employees, leave_period=self, carry_forward_leaves=carry_forward_leaves)
employee_records=employee_records, leave_period=self, carry_forward=carry_forward)
else:
grant_leave_alloc_for_employees(employees, self, carry_forward_leaves)
grant_leave_alloc_for_employees(employee_records, self, carry_forward)
else:
frappe.msgprint(_("No Employee Found"))
def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leaves=0):
def grant_leave_alloc_for_employees(employee_records, leave_period, carry_forward=0):
leave_allocations = []
existing_allocations_for = get_existing_allocations(employees, leave_period.name)
existing_allocations_for = get_existing_allocations(list(employee_records.keys()), leave_period.name)
leave_type_details = get_leave_type_details()
count=0
for employee in employees:
count = 0
for employee in employee_records.keys():
if employee in existing_allocations_for:
continue
count +=1
@@ -67,18 +68,24 @@ def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leave
for leave_policy_detail in leave_policy.leave_policy_details:
if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
leave_allocation = create_leave_allocation(employee, leave_policy_detail.leave_type,
leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward_leaves)
leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward, employee_records.get(employee))
leave_allocations.append(leave_allocation)
frappe.db.commit()
frappe.publish_progress(count*100/len(set(employees) - set(existing_allocations_for)), title = _("Allocating leaves..."))
frappe.publish_progress(count*100/len(set(employee_records.keys()) - set(existing_allocations_for)), title = _("Allocating leaves..."))
if leave_allocations:
frappe.msgprint(_("Leaves has been granted sucessfully"))
def get_existing_allocations(employees, leave_period):
leave_allocations = frappe.db.sql_list("""
select distinct employee from `tabLeave Allocation`
where leave_period=%s and employee in (%s) and docstatus=1
SELECT DISTINCT
employee
FROM `tabLeave Allocation`
WHERE
leave_period=%s
AND employee in (%s)
AND carry_forward=0
AND docstatus=1
""" % ('%s', ', '.join(['%s']*len(employees))), [leave_period] + employees)
if leave_allocations:
frappe.msgprint(_("Skipping Leave Allocation for the following employees, as Leave Allocation records already exists against them. {0}")
@@ -87,28 +94,36 @@ def get_existing_allocations(employees, leave_period):
def get_leave_type_details():
leave_type_details = frappe._dict()
leave_types = frappe.get_all("Leave Type", fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward"])
leave_types = frappe.get_all("Leave Type",
fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"])
for d in leave_types:
leave_type_details.setdefault(d.name, d)
return leave_type_details
def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_type_details, leave_period, carry_forward_leaves):
allocation = frappe.new_doc("Leave Allocation")
allocation.employee = employee
allocation.leave_type = leave_type
allocation.from_date = leave_period.from_date
allocation.to_date = leave_period.to_date
def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_type_details, leave_period, carry_forward, date_of_joining):
''' Creates leave allocation for the given employee in the provided leave period '''
if carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
carry_forward = 0
# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
if getdate(date_of_joining) > getdate(leave_period.from_date):
remaining_period = ((date_diff(leave_period.to_date, date_of_joining) + 1) / (date_diff(leave_period.to_date, leave_period.from_date) + 1))
new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
# Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1:
new_leaves_allocated = 0
allocation.new_leaves_allocated = new_leaves_allocated
allocation.leave_period = leave_period.name
if carry_forward_leaves:
if leave_type_details.get(leave_type).is_carry_forward:
allocation.carry_forward = carry_forward_leaves
allocation = frappe.get_doc(dict(
doctype="Leave Allocation",
employee=employee,
leave_type=leave_type,
from_date=leave_period.from_date,
to_date=leave_period.to_date,
new_leaves_allocated=new_leaves_allocated,
leave_period=leave_period.name,
carry_forward=carry_forward
))
allocation.save(ignore_permissions = True)
allocation.submit()
return allocation.name
return allocation.name

View File

@@ -12,6 +12,9 @@ def get_data():
},
{
'items': ['Employee Grade']
}
},
{
'items': ['Leave Allocation']
},
]
}

View File

@@ -12,16 +12,20 @@ class TestLeavePolicy(unittest.TestCase):
if random_leave_type:
random_leave_type = random_leave_type[0]
leave_type = frappe.get_doc("Leave Type", random_leave_type.name)
old_max_leaves_allowed = leave_type.max_leaves_allowed
leave_type.max_leaves_allowed = 2
leave_type.save()
leave_policy_details = {
"doctype": "Leave Policy",
"leave_policy_details": [{
"leave_type": leave_type.name,
"annual_allocation": leave_type.max_leaves_allowed + 1
}]
}
leave_policy = create_leave_policy(leave_type=leave_type.name, annual_allocation=leave_type.max_leaves_allowed + 1)
self.assertRaises(frappe.ValidationError, frappe.get_doc(leave_policy_details).insert)
self.assertRaises(frappe.ValidationError, leave_policy.insert)
def create_leave_policy(**args):
''' Returns an object of leave policy '''
args = frappe._dict(args)
return frappe.get_doc({
"doctype": "Leave Policy",
"leave_policy_details": [{
"leave_type": args.leave_type or "_Test Leave Type",
"annual_allocation": args.annual_allocation or 10
}]
})

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
@@ -14,10 +15,12 @@
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "leave_type_name",
"fieldtype": "Data",
"hidden": 0,
@@ -42,15 +45,17 @@
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "max_leaves_allowed",
"fieldtype": "Int",
"hidden": 0,
@@ -78,10 +83,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "applicable_after",
"fieldtype": "Int",
"hidden": 0,
@@ -109,10 +116,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "max_continuous_days_allowed",
"fieldtype": "Int",
"hidden": 0,
@@ -141,10 +150,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
@@ -171,10 +182,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_carry_forward",
"fieldtype": "Check",
"hidden": 0,
@@ -203,10 +216,13 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "is_lwp",
"fieldtype": "Check",
"hidden": 0,
@@ -233,10 +249,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_optional_leave",
"fieldtype": "Check",
"hidden": 0,
@@ -264,10 +282,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "allow_negative",
"fieldtype": "Check",
"hidden": 0,
@@ -294,10 +314,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "include_holiday",
"fieldtype": "Check",
"hidden": 0,
@@ -324,10 +346,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_compensatory",
"fieldtype": "Check",
"hidden": 0,
@@ -355,10 +379,81 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"depends_on": "eval: doc.is_carry_forward == 1",
"fetch_if_empty": 0,
"fieldname": "carry_forward_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Carry Forward",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"description": "Calculated in days",
"fetch_if_empty": 0,
"fieldname": "expire_carry_forwarded_leaves_after_days",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Expire Carry Forwarded Leaves (Days)",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "encashment",
"fieldtype": "Section Break",
"hidden": 0,
@@ -386,10 +481,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "allow_encashment",
"fieldtype": "Check",
"hidden": 0,
@@ -417,11 +514,13 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "allow_encashment",
"fetch_if_empty": 0,
"fieldname": "encashment_threshold_days",
"fieldtype": "Int",
"hidden": 0,
@@ -449,11 +548,13 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "allow_encashment",
"fetch_if_empty": 0,
"fieldname": "earning_component",
"fieldtype": "Link",
"hidden": 0,
@@ -482,10 +583,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "earned_leave",
"fieldtype": "Section Break",
"hidden": 0,
@@ -513,10 +616,12 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_earned_leave",
"fieldtype": "Check",
"hidden": 0,
@@ -544,11 +649,13 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_earned_leave",
"fetch_if_empty": 0,
"fieldname": "earned_leave_frequency",
"fieldtype": "Select",
"hidden": 0,
@@ -577,12 +684,14 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.5",
"depends_on": "is_earned_leave",
"fetch_if_empty": 0,
"fieldname": "rounding",
"fieldtype": "Select",
"hidden": 0,
@@ -611,17 +720,15 @@
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-flag",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-06-03 18:32:51.803472",
"modified": "2019-08-02 15:38:39.334283",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Type",
@@ -687,8 +794,8 @@
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 0,
"track_seen": 0
}
"track_seen": 0,
"track_views": 0
}

View File

@@ -2,9 +2,22 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import calendar
import frappe
from datetime import datetime
from frappe.utils import today
from frappe import _
from frappe.model.document import Document
class LeaveType(Document):
pass
def validate(self):
if self.is_lwp:
leave_allocation = frappe.get_all("Leave Allocation", filters={
'leave_type': self.name,
'from_date': ("<=", today()),
'to_date': (">=", today())
}, fields=['name'])
leave_allocation = [l['name'] for l in leave_allocation]
if leave_allocation:
frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec

View File

@@ -2,6 +2,25 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
test_records = frappe.get_test_records('Leave Type')
from frappe import _
test_records = frappe.get_test_records('Leave Type')
def create_leave_type(**args):
args = frappe._dict(args)
if frappe.db.exists("Leave Type", args.leave_type_name):
return frappe.get_doc("Leave Type", args.leave_type_name)
leave_type = frappe.get_doc({
"doctype": "Leave Type",
"leave_type_name": args.leave_type_name or "_Test Leave Type",
"include_holiday": args.include_holidays or 1,
"allow_encashment": args.allow_encashment or 0,
"is_earned_leave": args.is_earned_leave or 0,
"is_lwp": args.is_lwp or 0,
"is_carry_forward": args.is_carry_forward or 0,
"expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
"encashment_threshold_days": args.encashment_threshold_days or 5,
"earning_component": "Leave Encashment"
})
return leave_type

View File

@@ -69,7 +69,7 @@ frappe.ui.form.on('Payroll Entry', {
},
add_context_buttons: function(frm) {
if(frm.doc.salary_slips_submitted) {
if(frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
frm.events.add_bank_entry_button(frm);
} else if(frm.doc.salary_slips_created) {
frm.add_custom_button(__("Submit Salary Slip"), function() {

View File

@@ -13,14 +13,13 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
class PayrollEntry(Document):
def onload(self):
if not self.docstatus==1:
return
if not self.docstatus==1 or self.salary_slips_submitted:
return
# check if salary slips were manually submitted
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
if cint(entries) == len(self.employees) and not self.salary_slips_submitted:
self.db_set("salary_slips_submitted", 1)
self.reload()
if cint(entries) == len(self.employees):
self.set_onload("submitted_ss", True)
def on_submit(self):
self.create_salary_slips()
@@ -429,7 +428,6 @@ def get_start_end_dates(payroll_frequency, start_date=None, company=None):
'start_date': start_date, 'end_date': end_date
})
def get_frequency_kwargs(frequency_name):
frequency_dict = {
'monthly': {'months': 1},

View File

@@ -23,14 +23,9 @@
"grace_period_settings_auto_attendance_section",
"enable_entry_grace_period",
"late_entry_grace_period",
"consequence_after",
"consequence",
"column_break_18",
"enable_exit_grace_period",
"enable_different_consequence_for_early_exit",
"early_exit_grace_period",
"early_exit_consequence_after",
"early_exit_consequence"
"early_exit_grace_period"
],
"fields": [
{
@@ -107,21 +102,6 @@
"fieldtype": "Int",
"label": "Late Entry Grace Period"
},
{
"depends_on": "enable_entry_grace_period",
"description": "The number of occurrence after which the consequence is executed.",
"fieldname": "consequence_after",
"fieldtype": "Int",
"label": "Consequence after"
},
{
"default": "Half Day",
"depends_on": "enable_entry_grace_period",
"fieldname": "consequence",
"fieldtype": "Select",
"label": "Consequence",
"options": "Half Day\nAbsent"
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
@@ -132,13 +112,6 @@
"fieldtype": "Check",
"label": "Enable Exit Grace Period"
},
{
"default": "0",
"depends_on": "enable_exit_grace_period",
"fieldname": "enable_different_consequence_for_early_exit",
"fieldtype": "Check",
"label": "Enable Different Consequence for Early Exit"
},
{
"depends_on": "eval:doc.enable_exit_grace_period",
"description": "The time before the shift end time when check-out is considered as early (in minutes).",
@@ -146,21 +119,6 @@
"fieldtype": "Int",
"label": "Early Exit Grace Period"
},
{
"depends_on": "eval:doc.enable_exit_grace_period && doc.enable_different_consequence_for_early_exit",
"description": "The number of occurrence after which the consequence is executed.",
"fieldname": "early_exit_consequence_after",
"fieldtype": "Int",
"label": "Early Exit Consequence after"
},
{
"default": "Half Day",
"depends_on": "eval:doc.enable_exit_grace_period && doc.enable_different_consequence_for_early_exit",
"fieldname": "early_exit_consequence",
"fieldtype": "Select",
"label": "Early Exit Consequence",
"options": "Half Day\nAbsent"
},
{
"default": "60",
"description": "Time after the end of shift during which check-out is considered for attendance.",
@@ -178,7 +136,6 @@
"depends_on": "enable_auto_attendance",
"fieldname": "grace_period_settings_auto_attendance_section",
"fieldtype": "Section Break",
"hidden": 1,
"label": "Grace Period Settings For Auto Attendance"
},
{
@@ -201,7 +158,7 @@
"label": "Last Sync of Checkin"
}
],
"modified": "2019-06-10 06:02:44.272036",
"modified": "2019-07-30 01:05:24.660666",
"modified_by": "Administrator",
"module": "HR",
"name": "Shift Type",

View File

@@ -28,8 +28,8 @@ class ShiftType(Document):
logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time")
for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])):
single_shift_logs = list(group)
attendance_status, working_hours = self.get_attendance(single_shift_logs)
mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, self.name)
attendance_status, working_hours, late_entry, early_exit = self.get_attendance(single_shift_logs)
mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, self.name)
for employee in self.get_assigned_employee(self.process_attendance_after, True):
self.mark_absent_for_dates_with_no_attendance(employee)
@@ -39,12 +39,19 @@ class ShiftType(Document):
1. These logs belongs to an single shift, single employee and is not in a holiday date.
2. Logs are in chronological order
"""
total_working_hours = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on)
late_entry = early_exit = False
total_working_hours, in_time, out_time = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on)
if cint(self.enable_entry_grace_period) and in_time and in_time > logs[0].shift_start + timedelta(minutes=cint(self.late_entry_grace_period)):
late_entry = True
if cint(self.enable_exit_grace_period) and out_time and out_time < logs[0].shift_end - timedelta(minutes=cint(self.early_exit_grace_period)):
early_exit = True
if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent:
return 'Absent', total_working_hours
return 'Absent', total_working_hours, late_entry, early_exit
if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day:
return 'Half Day', total_working_hours
return 'Present', total_working_hours
return 'Half Day', total_working_hours, late_entry, early_exit
return 'Present', total_working_hours, late_entry, early_exit
def mark_absent_for_dates_with_no_attendance(self, employee):
"""Marks Absents for the given employee on working days in this shift which have no attendance marked.

View File

@@ -24,6 +24,18 @@ frappe.query_reports["Employee Leave Balance"] = {
"options": "Company",
"reqd": 1,
"default": frappe.defaults.get_user_default("Company")
},
{
"fieldname":"department",
"label": __("Department"),
"fieldtype": "Link",
"options": "Department",
},
{
"fieldname":"employee",
"label": __("Employee"),
"fieldtype": "Link",
"options": "Employee",
}
]
}

View File

@@ -4,8 +4,9 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt
from erpnext.hr.doctype.leave_application.leave_application \
import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period
import get_leave_balance_on, get_leaves_for_period
def execute(filters=None):
@@ -30,17 +31,28 @@ def get_columns(leave_types):
return columns
def get_conditions(filters):
conditions = {
"status": "Active",
"company": filters.company,
}
if filters.get("department"):
conditions.update({"department": filters.get("department")})
if filters.get("employee"):
conditions.update({"employee": filters.get("employee")})
return conditions
def get_data(filters, leave_types):
user = frappe.session.user
allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date)
allocation_records_based_on_from_date = get_leave_allocation_records(filters.from_date)
conditions = get_conditions(filters)
if filters.to_date <= filters.from_date:
frappe.throw(_("From date can not be greater than than To date"))
active_employees = frappe.get_all("Employee",
filters = { "status": "Active", "company": filters.company},
fields = ["name", "employee_name", "department", "user_id"])
filters=conditions,
fields=["name", "employee_name", "department", "user_id"])
data = []
for employee in active_employees:
@@ -50,16 +62,14 @@ def get_data(filters, leave_types):
for leave_type in leave_types:
# leaves taken
leaves_taken = get_approved_leaves_for_period(employee.name, leave_type,
filters.from_date, filters.to_date)
leaves_taken = get_leaves_for_period(employee.name, leave_type,
filters.from_date, filters.to_date) * -1
# opening balance
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date,
allocation_records_based_on_to_date.get(employee.name, frappe._dict()))
opening = get_total_allocated_leaves(employee.name, leave_type, filters.from_date, filters.to_date)
# closing balance
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date,
allocation_records_based_on_to_date.get(employee.name, frappe._dict()))
closing = flt(opening) - flt(leaves_taken)
row += [opening, leaves_taken, closing]
@@ -84,3 +94,18 @@ def get_approvers(department):
where parent = %s and parentfield = 'leave_approvers'""", (d), as_dict=True)])
return approvers
def get_total_allocated_leaves(employee, leave_type, from_date, to_date):
''' Returns leave allocation between from date and to date '''
leave_allocation_records = frappe.db.get_all('Leave Ledger Entry', filters={
'docstatus': 1,
'is_expired': 0,
'leave_type': leave_type,
'employee': employee,
'transaction_type': 'Leave Allocation'
}, or_filters={
'from_date': ['between', (from_date, to_date)],
'to_date': ['between', (from_date, to_date)]
}, fields=['SUM(leaves) as leaves'])
return flt(leave_allocation_records[0].get('leaves')) if leave_allocation_records else flt(0)

View File

@@ -25,6 +25,7 @@ def execute(filters=None):
leave_types = frappe.db.sql("""select name from `tabLeave Type`""", as_list=True)
leave_list = [d[0] for d in leave_types]
columns.extend(leave_list)
columns.extend([_("Total Late Entries") + ":Float:120", _("Total Early Exits") + ":Float:120"])
for emp in sorted(att_map):
emp_det = emp_map.get(emp)
@@ -65,6 +66,10 @@ def execute(filters=None):
leave_details = frappe.db.sql("""select leave_type, status, count(*) as count from `tabAttendance`\
where leave_type is not NULL %s group by leave_type, status""" % conditions, filters, as_dict=1)
time_default_counts = frappe.db.sql("""select (select count(*) from `tabAttendance` where \
late_entry = 1 %s) as late_entry_count, (select count(*) from tabAttendance where \
early_exit = 1 %s) as early_exit_count""" % (conditions, conditions), filters)
leaves = {}
for d in leave_details:
@@ -80,7 +85,8 @@ def execute(filters=None):
row.append(leaves[d])
else:
row.append("0.0")
row.extend([time_default_counts[0][0],time_default_counts[0][1]])
data.append(row)
return columns, data

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe, erpnext
from frappe import _
from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr
from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr, add_days, today
from frappe.model.document import Document
from frappe.desk.form import assign_to
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
@@ -36,12 +36,18 @@ class EmployeeBoardingController(Document):
}).insert(ignore_permissions=True)
self.db_set("project", project.name)
self.db_set("boarding_status", "Pending")
self.reload()
self.create_task_and_notify_user()
def create_task_and_notify_user(self):
# create the task for the given project and assign to the concerned person
for activity in self.activities:
if activity.task:
continue
task = frappe.get_doc({
"doctype": "Task",
"project": project.name,
"project": self.project,
"subject": activity.activity_name + " : " + self.employee_name,
"description": activity.description,
"department": self.department,
@@ -69,6 +75,7 @@ class EmployeeBoardingController(Document):
'doctype' : task.doctype,
'name' : task.name,
'description' : task.description or task.subject,
'notify': self.notify_users_by_email
}
assign_to.add(args)
@@ -263,6 +270,21 @@ def get_leave_period(from_date, to_date, company):
if leave_period:
return leave_period
def generate_leave_encashment():
''' Generates a draft leave encashment on allocation expiry '''
from erpnext.hr.doctype.leave_encashment.leave_encashment import create_leave_encashment
if frappe.db.get_single_value('HR Settings', 'auto_leave_encashment'):
leave_type = frappe.get_all('Leave Type', filters={'allow_encashment': 1}, fields=['name'])
leave_type=[l['name'] for l in leave_type]
leave_allocation = frappe.get_all("Leave Allocation", filters={
'to_date': add_days(today(), -1),
'leave_type': ('in', leave_type)
}, fields=['employee', 'leave_period', 'leave_type', 'to_date', 'total_leaves_allocated', 'new_leaves_allocated'])
create_leave_encashment(leave_allocation=leave_allocation)
def allocate_earned_leaves():
'''Allocate earned leaves to Employees'''
e_leave_types = frappe.get_all("Leave Type",
@@ -270,31 +292,43 @@ def allocate_earned_leaves():
filters={'is_earned_leave' : 1})
today = getdate()
divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12}
if e_leave_types:
for e_leave_type in e_leave_types:
leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where '{0}'
between from_date and to_date and docstatus=1 and leave_type='{1}'"""
.format(today, e_leave_type.name), as_dict=1)
for allocation in leave_allocations:
leave_policy = get_employee_leave_policy(allocation.employee)
if not leave_policy:
continue
if not e_leave_type.earned_leave_frequency == "Monthly":
if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency):
continue
annual_allocation = frappe.db.sql("""select annual_allocation from `tabLeave Policy Detail`
where parent=%s and leave_type=%s""", (leave_policy.name, e_leave_type.name))
if annual_allocation and annual_allocation[0]:
earned_leaves = flt(annual_allocation[0][0]) / divide_by_frequency[e_leave_type.earned_leave_frequency]
if e_leave_type.rounding == "0.5":
earned_leaves = round(earned_leaves * 2) / 2
else:
earned_leaves = round(earned_leaves)
allocated_leaves = frappe.db.get_value('Leave Allocation', allocation.name, 'total_leaves_allocated')
new_allocation = flt(allocated_leaves) + flt(earned_leaves)
new_allocation = new_allocation if new_allocation <= e_leave_type.max_leaves_allowed else e_leave_type.max_leaves_allowed
frappe.db.set_value('Leave Allocation', allocation.name, 'total_leaves_allocated', new_allocation)
for e_leave_type in e_leave_types:
leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where %s
between from_date and to_date and docstatus=1 and leave_type=%s""", (today, e_leave_type.name), as_dict=1)
for allocation in leave_allocations:
leave_policy = get_employee_leave_policy(allocation.employee)
if not leave_policy:
continue
if not e_leave_type.earned_leave_frequency == "Monthly":
if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency):
continue
annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={
'parent': leave_policy.name,
'leave_type': e_leave_type.name
}, fieldname=['annual_allocation'])
if annual_allocation:
earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency]
if e_leave_type.rounding == "0.5":
earned_leaves = round(earned_leaves * 2) / 2
else:
earned_leaves = round(earned_leaves)
allocation = frappe.get_doc('Leave Allocation', allocation.name)
new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
new_allocation = new_allocation if new_allocation <= e_leave_type.max_leaves_allowed else e_leave_type.max_leaves_allowed
if new_allocation == allocation.total_leaves_allocated:
continue
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
create_earned_leave_ledger_entry(allocation, earned_leaves, today)
def create_earned_leave_ledger_entry(allocation, earned_leaves, date):
''' Create leave ledger entry based on the earned leave frequency '''
allocation.new_leaves_allocated = earned_leaves
allocation.from_date = date
allocation.unused_leaves = 0
allocation.create_leave_ledger_entry()
def check_frequency_hit(from_date, to_date, frequency):
'''Return True if current date matches frequency'''

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@ from erpnext.setup.utils import get_exchange_rate
from frappe.website.website_generator import WebsiteGenerator
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.get_item_details import get_price_list_rate
from frappe.core.doctype.version.version import get_diff
import functools
@@ -763,3 +764,52 @@ def add_additional_cost(stock_entry, work_order):
'description': name[0],
'amount': items.get(name[0])
})
@frappe.whitelist()
def get_bom_diff(bom1, bom2):
from frappe.model import table_fields
doc1 = frappe.get_doc('BOM', bom1)
doc2 = frappe.get_doc('BOM', bom2)
out = get_diff(doc1, doc2)
out.row_changed = []
out.added = []
out.removed = []
meta = doc1.meta
identifiers = {
'operations': 'operation',
'items': 'item_code',
'scrap_items': 'item_code',
'exploded_items': 'item_code'
}
for df in meta.fields:
old_value, new_value = doc1.get(df.fieldname), doc2.get(df.fieldname)
if df.fieldtype in table_fields:
identifier = identifiers[df.fieldname]
# make maps
old_row_by_identifier, new_row_by_identifier = {}, {}
for d in old_value:
old_row_by_identifier[d.get(identifier)] = d
for d in new_value:
new_row_by_identifier[d.get(identifier)] = d
# check rows for additions, changes
for i, d in enumerate(new_value):
if d.get(identifier) in old_row_by_identifier:
diff = get_diff(old_row_by_identifier[d.get(identifier)], d, for_child=True)
if diff and diff.changed:
out.row_changed.append((df.fieldname, i, d.get(identifier), diff.changed))
else:
out.added.append([df.fieldname, d.as_dict()])
# check for deletions
for d in old_value:
if not d.get(identifier) in new_row_by_identifier:
out.removed.append([df.fieldname, d.as_dict()])
return out

View File

@@ -320,7 +320,8 @@ class ProductionPlan(Document):
'qty': data.get("stock_qty") * item.get("qty"),
'production_plan': self.name,
'company': self.company,
'fg_warehouse': item.get("fg_warehouse")
'fg_warehouse': item.get("fg_warehouse"),
'update_consumed_material_cost_in_project': 0
})
work_order = self.create_work_order(data)
@@ -430,7 +431,7 @@ def download_raw_materials(production_plan):
continue
item_list.append(['', '', '', '', bin_dict.get('warehouse'),
bin_dict.get('projected_qty'), bin_dict.get('actual_qty')])
bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)])
build_csv_response(item_list, doc.name)
@@ -507,8 +508,8 @@ def get_material_request_items(row, sales_order,
required_qty = 0
if ignore_existing_ordered_qty or bin_dict.get("projected_qty", 0) < 0:
required_qty = total_qty
elif total_qty > bin_dict.get("projected_qty"):
required_qty = total_qty - bin_dict.get("projected_qty")
elif total_qty > bin_dict.get("projected_qty", 0):
required_qty = total_qty - bin_dict.get("projected_qty", 0)
if required_qty > 0 and required_qty < row['min_order_qty']:
required_qty = row['min_order_qty']
item_group_defaults = get_item_group_defaults(row.item_code, company)

View File

@@ -1,484 +1,504 @@
{
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-01-10 16:34:16",
"doctype": "DocType",
"document_type": "Setup",
"field_order": [
"item",
"naming_series",
"status",
"production_item",
"item_name",
"image",
"bom_no",
"allow_alternative_item",
"use_multi_level_bom",
"skip_transfer",
"column_break1",
"company",
"qty",
"material_transferred_for_manufacturing",
"produced_qty",
"sales_order",
"project",
"from_wip_warehouse",
"warehouses",
"wip_warehouse",
"fg_warehouse",
"column_break_12",
"scrap_warehouse",
"required_items_section",
"required_items",
"time",
"planned_start_date",
"actual_start_date",
"column_break_13",
"planned_end_date",
"actual_end_date",
"expected_delivery_date",
"operations_section",
"transfer_material_against",
"operations",
"section_break_22",
"planned_operating_cost",
"actual_operating_cost",
"additional_operating_cost",
"column_break_24",
"total_operating_cost",
"more_info",
"description",
"stock_uom",
"column_break2",
"material_request",
"material_request_item",
"sales_order_item",
"production_plan",
"production_plan_item",
"product_bundle_item",
"amended_from"
],
"fields": [
{
"fieldname": "item",
"fieldtype": "Section Break",
"options": "fa fa-gift"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"options": "MFG-WO-.YYYY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
},
{
"default": "Draft",
"depends_on": "eval:!doc.__islocal",
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled",
"read_only": 1,
"reqd": 1,
"search_index": 1
},
{
"fieldname": "production_item",
"fieldtype": "Link",
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Item To Manufacture",
"oldfieldname": "production_item",
"oldfieldtype": "Link",
"options": "Item",
"reqd": 1
},
{
"depends_on": "eval:doc.production_item",
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
"read_only": 1
},
{
"fetch_from": "production_item.image",
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"label": "Image",
"options": "image",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "bom_no",
"fieldtype": "Link",
"label": "BOM No",
"oldfieldname": "bom_no",
"oldfieldtype": "Link",
"options": "BOM",
"reqd": 1
},
{
"default": "0",
"fieldname": "allow_alternative_item",
"fieldtype": "Check",
"label": "Allow Alternative Item"
},
{
"default": "1",
"description": "Plan material for sub-assemblies",
"fieldname": "use_multi_level_bom",
"fieldtype": "Check",
"label": "Use Multi-Level BOM",
"print_hide": 1
},
{
"default": "0",
"description": "Check if material transfer entry is not required",
"fieldname": "skip_transfer",
"fieldtype": "Check",
"label": "Skip Material Transfer to WIP Warehouse"
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
"remember_last_selected_value": 1,
"reqd": 1
},
{
"fieldname": "qty",
"fieldtype": "Float",
"label": "Qty To Manufacture",
"oldfieldname": "qty",
"oldfieldtype": "Currency",
"reqd": 1
},
{
"default": "0",
"depends_on": "eval:doc.docstatus==1 && doc.skip_transfer==0",
"fieldname": "material_transferred_for_manufacturing",
"fieldtype": "Float",
"label": "Material Transferred for Manufacturing",
"no_copy": 1,
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:doc.docstatus==1",
"fieldname": "produced_qty",
"fieldtype": "Float",
"label": "Manufactured Qty",
"no_copy": 1,
"oldfieldname": "produced_qty",
"oldfieldtype": "Currency",
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "sales_order",
"fieldtype": "Link",
"in_global_search": 1,
"label": "Sales Order",
"options": "Sales Order"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"oldfieldname": "project",
"oldfieldtype": "Link",
"options": "Project"
},
{
"default": "0",
"depends_on": "skip_transfer",
"fieldname": "from_wip_warehouse",
"fieldtype": "Check",
"label": "Backflush Raw Materials From Work-in-Progress Warehouse"
},
{
"fieldname": "warehouses",
"fieldtype": "Section Break",
"label": "Warehouses",
"options": "fa fa-building"
},
{
"fieldname": "wip_warehouse",
"fieldtype": "Link",
"label": "Work-in-Progress Warehouse",
"options": "Warehouse"
},
{
"fieldname": "fg_warehouse",
"fieldtype": "Link",
"label": "Target Warehouse",
"options": "Warehouse"
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"fieldname": "scrap_warehouse",
"fieldtype": "Link",
"label": "Scrap Warehouse",
"options": "Warehouse"
},
{
"fieldname": "required_items_section",
"fieldtype": "Section Break",
"label": "Required Items"
},
{
"fieldname": "required_items",
"fieldtype": "Table",
"label": "Required Items",
"no_copy": 1,
"options": "Work Order Item",
"print_hide": 1
},
{
"fieldname": "time",
"fieldtype": "Section Break",
"label": "Time",
"options": "fa fa-time"
},
{
"allow_on_submit": 1,
"default": "now",
"fieldname": "planned_start_date",
"fieldtype": "Datetime",
"label": "Planned Start Date",
"reqd": 1
},
{
"fieldname": "actual_start_date",
"fieldtype": "Datetime",
"label": "Actual Start Date",
"read_only": 1
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
"fieldname": "planned_end_date",
"fieldtype": "Datetime",
"label": "Planned End Date",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "actual_end_date",
"fieldtype": "Datetime",
"label": "Actual End Date",
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "expected_delivery_date",
"fieldtype": "Date",
"label": "Expected Delivery Date"
},
{
"fieldname": "operations_section",
"fieldtype": "Section Break",
"label": "Operations",
"options": "fa fa-wrench"
},
{
"default": "Work Order",
"depends_on": "operations",
"fieldname": "transfer_material_against",
"fieldtype": "Select",
"label": "Transfer Material Against",
"options": "\nWork Order\nJob Card"
},
{
"fieldname": "operations",
"fieldtype": "Table",
"label": "Operations",
"options": "Work Order Operation",
"read_only": 1
},
{
"depends_on": "operations",
"fieldname": "section_break_22",
"fieldtype": "Section Break",
"label": "Operation Cost"
},
{
"fieldname": "planned_operating_cost",
"fieldtype": "Currency",
"label": "Planned Operating Cost",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "actual_operating_cost",
"fieldtype": "Currency",
"label": "Actual Operating Cost",
"no_copy": 1,
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "additional_operating_cost",
"fieldtype": "Currency",
"label": "Additional Operating Cost",
"no_copy": 1,
"options": "Company:company:default_currency"
},
{
"fieldname": "column_break_24",
"fieldtype": "Column Break"
},
{
"fieldname": "total_operating_cost",
"fieldtype": "Currency",
"label": "Total Operating Cost",
"no_copy": 1,
"options": "Company:company:default_currency",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "more_info",
"fieldtype": "Section Break",
"label": "More Information",
"options": "fa fa-file-text"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Item Description",
"read_only": 1
},
{
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"oldfieldname": "stock_uom",
"oldfieldtype": "Data",
"options": "UOM",
"read_only": 1
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
"width": "50%"
},
{
"description": "Manufacture against Material Request",
"fieldname": "material_request",
"fieldtype": "Link",
"label": "Material Request",
"options": "Material Request"
},
{
"fieldname": "material_request_item",
"fieldtype": "Data",
"hidden": 1,
"label": "Material Request Item",
"read_only": 1
},
{
"fieldname": "sales_order_item",
"fieldtype": "Data",
"hidden": 1,
"label": "Sales Order Item",
"read_only": 1
},
{
"fieldname": "production_plan",
"fieldtype": "Link",
"label": "Production Plan",
"no_copy": 1,
"options": "Production Plan",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "production_plan_item",
"fieldtype": "Data",
"label": "Production Plan Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "product_bundle_item",
"fieldtype": "Link",
"label": "Product Bundle Item",
"no_copy": 1,
"options": "Item",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Amended From",
"no_copy": 1,
"oldfieldname": "amended_from",
"oldfieldtype": "Data",
"options": "Work Order",
"read_only": 1
}
],
"icon": "fa fa-cogs",
"idx": 1,
"image_field": "image",
"is_submittable": 1,
"modified": "2019-05-27 09:36:16.707719",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"import": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Manufacturing User",
"set_user_permissions": 1,
"share": 1,
"submit": 1,
"write": 1
},
{
"read": 1,
"report": 1,
"role": "Stock User"
}
],
"sort_order": "ASC",
"title_field": "production_item",
"track_changes": 1,
"track_seen": 1
}
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-01-10 16:34:16",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"item",
"naming_series",
"status",
"production_item",
"item_name",
"image",
"bom_no",
"column_break1",
"company",
"qty",
"material_transferred_for_manufacturing",
"produced_qty",
"sales_order",
"project",
"settings_section",
"allow_alternative_item",
"use_multi_level_bom",
"column_break_18",
"skip_transfer",
"from_wip_warehouse",
"update_consumed_material_cost_in_project",
"warehouses",
"wip_warehouse",
"fg_warehouse",
"column_break_12",
"scrap_warehouse",
"required_items_section",
"required_items",
"time",
"planned_start_date",
"actual_start_date",
"column_break_13",
"planned_end_date",
"actual_end_date",
"expected_delivery_date",
"operations_section",
"transfer_material_against",
"operations",
"section_break_22",
"planned_operating_cost",
"actual_operating_cost",
"additional_operating_cost",
"column_break_24",
"total_operating_cost",
"more_info",
"description",
"stock_uom",
"column_break2",
"material_request",
"material_request_item",
"sales_order_item",
"production_plan",
"production_plan_item",
"product_bundle_item",
"amended_from"
],
"fields": [
{
"fieldname": "item",
"fieldtype": "Section Break",
"options": "fa fa-gift"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"options": "MFG-WO-.YYYY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
},
{
"default": "Draft",
"depends_on": "eval:!doc.__islocal",
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled",
"read_only": 1,
"reqd": 1,
"search_index": 1
},
{
"fieldname": "production_item",
"fieldtype": "Link",
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Item To Manufacture",
"oldfieldname": "production_item",
"oldfieldtype": "Link",
"options": "Item",
"reqd": 1
},
{
"depends_on": "eval:doc.production_item",
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
"read_only": 1
},
{
"fetch_from": "production_item.image",
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"label": "Image",
"options": "image",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "bom_no",
"fieldtype": "Link",
"label": "BOM No",
"oldfieldname": "bom_no",
"oldfieldtype": "Link",
"options": "BOM",
"reqd": 1
},
{
"default": "0",
"fieldname": "allow_alternative_item",
"fieldtype": "Check",
"label": "Allow Alternative Item"
},
{
"default": "1",
"description": "Plan material for sub-assemblies",
"fieldname": "use_multi_level_bom",
"fieldtype": "Check",
"label": "Use Multi-Level BOM",
"print_hide": 1
},
{
"default": "0",
"description": "Check if material transfer entry is not required",
"fieldname": "skip_transfer",
"fieldtype": "Check",
"label": "Skip Material Transfer to WIP Warehouse"
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
"remember_last_selected_value": 1,
"reqd": 1
},
{
"fieldname": "qty",
"fieldtype": "Float",
"label": "Qty To Manufacture",
"oldfieldname": "qty",
"oldfieldtype": "Currency",
"reqd": 1
},
{
"default": "0",
"depends_on": "eval:doc.docstatus==1 && doc.skip_transfer==0",
"fieldname": "material_transferred_for_manufacturing",
"fieldtype": "Float",
"label": "Material Transferred for Manufacturing",
"no_copy": 1,
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:doc.docstatus==1",
"fieldname": "produced_qty",
"fieldtype": "Float",
"label": "Manufactured Qty",
"no_copy": 1,
"oldfieldname": "produced_qty",
"oldfieldtype": "Currency",
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "sales_order",
"fieldtype": "Link",
"in_global_search": 1,
"label": "Sales Order",
"options": "Sales Order"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"oldfieldname": "project",
"oldfieldtype": "Link",
"options": "Project"
},
{
"default": "0",
"depends_on": "skip_transfer",
"fieldname": "from_wip_warehouse",
"fieldtype": "Check",
"label": "Backflush Raw Materials From Work-in-Progress Warehouse"
},
{
"fieldname": "warehouses",
"fieldtype": "Section Break",
"label": "Warehouses",
"options": "fa fa-building"
},
{
"fieldname": "wip_warehouse",
"fieldtype": "Link",
"label": "Work-in-Progress Warehouse",
"options": "Warehouse"
},
{
"fieldname": "fg_warehouse",
"fieldtype": "Link",
"label": "Target Warehouse",
"options": "Warehouse"
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"fieldname": "scrap_warehouse",
"fieldtype": "Link",
"label": "Scrap Warehouse",
"options": "Warehouse"
},
{
"fieldname": "required_items_section",
"fieldtype": "Section Break",
"label": "Required Items"
},
{
"fieldname": "required_items",
"fieldtype": "Table",
"label": "Required Items",
"no_copy": 1,
"options": "Work Order Item",
"print_hide": 1
},
{
"fieldname": "time",
"fieldtype": "Section Break",
"label": "Time",
"options": "fa fa-time"
},
{
"allow_on_submit": 1,
"default": "now",
"fieldname": "planned_start_date",
"fieldtype": "Datetime",
"label": "Planned Start Date",
"reqd": 1
},
{
"fieldname": "actual_start_date",
"fieldtype": "Datetime",
"label": "Actual Start Date",
"read_only": 1
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
"fieldname": "planned_end_date",
"fieldtype": "Datetime",
"label": "Planned End Date",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "actual_end_date",
"fieldtype": "Datetime",
"label": "Actual End Date",
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "expected_delivery_date",
"fieldtype": "Date",
"label": "Expected Delivery Date"
},
{
"fieldname": "operations_section",
"fieldtype": "Section Break",
"label": "Operations",
"options": "fa fa-wrench"
},
{
"default": "Work Order",
"depends_on": "operations",
"fieldname": "transfer_material_against",
"fieldtype": "Select",
"label": "Transfer Material Against",
"options": "\nWork Order\nJob Card"
},
{
"fieldname": "operations",
"fieldtype": "Table",
"label": "Operations",
"options": "Work Order Operation",
"read_only": 1
},
{
"depends_on": "operations",
"fieldname": "section_break_22",
"fieldtype": "Section Break",
"label": "Operation Cost"
},
{
"fieldname": "planned_operating_cost",
"fieldtype": "Currency",
"label": "Planned Operating Cost",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "actual_operating_cost",
"fieldtype": "Currency",
"label": "Actual Operating Cost",
"no_copy": 1,
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "additional_operating_cost",
"fieldtype": "Currency",
"label": "Additional Operating Cost",
"no_copy": 1,
"options": "Company:company:default_currency"
},
{
"fieldname": "column_break_24",
"fieldtype": "Column Break"
},
{
"fieldname": "total_operating_cost",
"fieldtype": "Currency",
"label": "Total Operating Cost",
"no_copy": 1,
"options": "Company:company:default_currency",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "more_info",
"fieldtype": "Section Break",
"label": "More Information",
"options": "fa fa-file-text"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Item Description",
"read_only": 1
},
{
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"oldfieldname": "stock_uom",
"oldfieldtype": "Data",
"options": "UOM",
"read_only": 1
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
"width": "50%"
},
{
"description": "Manufacture against Material Request",
"fieldname": "material_request",
"fieldtype": "Link",
"label": "Material Request",
"options": "Material Request"
},
{
"fieldname": "material_request_item",
"fieldtype": "Data",
"hidden": 1,
"label": "Material Request Item",
"read_only": 1
},
{
"fieldname": "sales_order_item",
"fieldtype": "Data",
"hidden": 1,
"label": "Sales Order Item",
"read_only": 1
},
{
"fieldname": "production_plan",
"fieldtype": "Link",
"label": "Production Plan",
"no_copy": 1,
"options": "Production Plan",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "production_plan_item",
"fieldtype": "Data",
"label": "Production Plan Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "product_bundle_item",
"fieldtype": "Link",
"label": "Product Bundle Item",
"no_copy": 1,
"options": "Item",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Amended From",
"no_copy": 1,
"oldfieldname": "amended_from",
"oldfieldtype": "Data",
"options": "Work Order",
"read_only": 1
},
{
"fieldname": "settings_section",
"fieldtype": "Section Break",
"label": "Settings"
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
},
{
"default": "1",
"fieldname": "update_consumed_material_cost_in_project",
"fieldtype": "Check",
"label": "Update Consumed Material Cost In Project"
}
],
"icon": "fa fa-cogs",
"idx": 1,
"image_field": "image",
"is_submittable": 1,
"modified": "2019-07-31 00:13:38.218277",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"import": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Manufacturing User",
"set_user_permissions": 1,
"share": 1,
"submit": 1,
"write": 1
},
{
"read": 1,
"report": 1,
"role": "Stock User"
}
],
"sort_field": "modified",
"sort_order": "ASC",
"title_field": "production_item",
"track_changes": 1,
"track_seen": 1
}

View File

@@ -0,0 +1,213 @@
frappe.pages['bom-comparison-tool'].on_page_load = function(wrapper) {
var page = frappe.ui.make_app_page({
parent: wrapper,
title: __('BOM Comparison Tool'),
single_column: true
});
new erpnext.BOMComparisonTool(page);
}
erpnext.BOMComparisonTool = class BOMComparisonTool {
constructor(page) {
this.page = page;
this.make_form();
}
make_form() {
this.form = new frappe.ui.FieldGroup({
fields: [
{
label: __('BOM 1'),
fieldname: 'name1',
fieldtype: 'Link',
options: 'BOM',
change: () => this.fetch_and_render()
},
{
fieldtype: 'Column Break'
},
{
label: __('BOM 2'),
fieldname: 'name2',
fieldtype: 'Link',
options: 'BOM',
change: () => this.fetch_and_render()
},
{
fieldtype: 'Section Break'
},
{
fieldtype: 'HTML',
fieldname: 'preview'
}
],
body: this.page.body
});
this.form.make();
}
fetch_and_render() {
let { name1, name2 } = this.form.get_values();
if (!(name1 && name2)) {
this.form.get_field('preview').html('');
return;
}
// set working state
this.form.get_field('preview').html(`
<div class="text-muted margin-top">
${__("Fetching...")}
</div>
`);
frappe.call('erpnext.manufacturing.doctype.bom.bom.get_bom_diff', {
bom1: name1,
bom2: name2
}).then(r => {
let diff = r.message;
frappe.model.with_doctype('BOM', () => {
this.render('BOM', name1, name2, diff);
});
});
}
render(doctype, name1, name2, diff) {
let change_html = (title, doctype, changed) => {
let values_changed = this.get_changed_values(doctype, changed)
.map(change => {
let [fieldname, value1, value2] = change;
return `
<tr>
<td>${frappe.meta.get_label(doctype, fieldname)}</td>
<td>${value1}</td>
<td>${value2}</td>
</tr>
`;
})
.join('');
return `
<h4 class="margin-top">${title}</h4>
<div>
<table class="table table-bordered">
<tr>
<th width="33%">${__('Field')}</th>
<th width="33%">${name1}</th>
<th width="33%">${name2}</th>
</tr>
${values_changed}
</table>
</div>
`;
}
let value_changes = change_html(__('Values Changed'), doctype, diff.changed);
let row_changes_by_fieldname = group_items(diff.row_changed, change => change[0]);
let table_changes = Object.keys(row_changes_by_fieldname).map(fieldname => {
let changes = row_changes_by_fieldname[fieldname];
let df = frappe.meta.get_docfield(doctype, fieldname);
let html = changes.map(change => {
let [fieldname,, item_code, changes] = change;
let df = frappe.meta.get_docfield(doctype, fieldname);
let child_doctype = df.options;
let values_changed = this.get_changed_values(child_doctype, changes);
return values_changed.map((change, i) => {
let [fieldname, value1, value2] = change;
let th = i === 0
? `<th rowspan="${values_changed.length}">${item_code}</th>`
: '';
return `
<tr>
${th}
<td>${frappe.meta.get_label(child_doctype, fieldname)}</td>
<td>${value1}</td>
<td>${value2}</td>
</tr>
`;
}).join('');
}).join('');
return `
<h4 class="margin-top">${__('Changes in {0}', [df.label])}</h4>
<table class="table table-bordered">
<tr>
<th width="25%">${__('Item Code')}</th>
<th width="25%">${__('Field')}</th>
<th width="25%">${name1}</th>
<th width="25%">${name2}</th>
</tr>
${html}
</table>
`;
}).join('');
let get_added_removed_html = (title, grouped_items) => {
return Object.keys(grouped_items).map(fieldname => {
let rows = grouped_items[fieldname];
let df = frappe.meta.get_docfield(doctype, fieldname);
let fields = frappe.meta.get_docfields(df.options)
.filter(df => df.in_list_view);
let html = rows.map(row => {
let [, doc] = row;
let cells = fields
.map(df => `<td>${doc[df.fieldname]}</td>`)
.join('');
return `<tr>${cells}</tr>`;
}).join('');
let header = fields.map(df => `<th>${df.label}</th>`).join('');
return `
<h4 class="margin-top">${$.format(title, [df.label])}</h4>
<table class="table table-bordered">
<tr>${header}</tr>
${html}
</table>
`;
}).join('');
};
let added_by_fieldname = group_items(diff.added, change => change[0]);
let removed_by_fieldname = group_items(diff.removed, change => change[0]);
let added_html = get_added_removed_html(__('Rows Added in {0}'), added_by_fieldname);
let removed_html = get_added_removed_html(__('Rows Removed in {0}'), removed_by_fieldname);
let html = `
${value_changes}
${table_changes}
${added_html}
${removed_html}
`;
this.form.get_field('preview').html(html);
}
get_changed_values(doctype, changed) {
return changed.filter(change => {
let [fieldname, value1, value2] = change;
if (!value1) value1 = '';
if (!value2) value2 = '';
if (value1 === value2) return false;
let df = frappe.meta.get_docfield(doctype, fieldname);
if (!df) return false;
if (df.hidden) return false;
return true;
});
}
};
function group_items(array, fn) {
return array.reduce((acc, item) => {
let key = fn(item);
acc[key] = acc[key] || [];
acc[key].push(item);
return acc;
}, {});
}

View File

@@ -0,0 +1,30 @@
{
"content": null,
"creation": "2019-07-29 13:24:38.201981",
"docstatus": 0,
"doctype": "Page",
"idx": 0,
"modified": "2019-07-29 13:24:38.201981",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "bom-comparison-tool",
"owner": "Administrator",
"page_name": "BOM Comparison Tool",
"restrict_to_domain": "Manufacturing",
"roles": [
{
"role": "System Manager"
},
{
"role": "Manufacturing User"
},
{
"role": "Manufacturing Manager"
}
],
"script": null,
"standard": "Yes",
"style": null,
"system_page": 0,
"title": "BOM Comparison Tool"
}

View File

@@ -596,6 +596,7 @@ erpnext.patches.v12_0.rename_pricing_rule_child_doctypes
erpnext.patches.v12_0.move_target_distribution_from_parent_to_child
erpnext.patches.v12_0.stock_entry_enhancements
erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019 #25-06-2019
erpnext.patches.v12_0.make_item_manufacturer
erpnext.patches.v12_0.move_item_tax_to_item_tax_template
erpnext.patches.v11_1.set_variant_based_on
erpnext.patches.v11_1.woocommerce_set_creation_user
@@ -606,7 +607,6 @@ erpnext.patches.v11_1.delete_scheduling_tool
erpnext.patches.v12_0.rename_tolerance_fields
erpnext.patches.v12_0.make_custom_fields_for_bank_remittance #14-06-2019
execute:frappe.delete_doc_if_exists("Page", "support-analytics")
erpnext.patches.v12_0.make_item_manufacturer
erpnext.patches.v12_0.remove_patient_medical_record_page
erpnext.patches.v11_1.move_customer_lead_to_dynamic_column
erpnext.patches.v11_1.set_default_action_for_quality_inspection
@@ -626,3 +626,4 @@ erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
erpnext.patches.v12_0.update_ewaybill_field_position
erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
erpnext.patches.v12_0.generate_leave_ledger_entries

View File

@@ -0,0 +1,87 @@
# Copyright (c) 2018, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import getdate, today
def execute():
""" Generates leave ledger entries for leave allocation/application/encashment
for last allocation """
frappe.reload_doc("HR", "doctype", "Leave Ledger Entry")
frappe.reload_doc("HR", "doctype", "Leave Encashment")
if frappe.db.a_row_exists("Leave Ledger Entry"):
return
if not frappe.get_meta("Leave Allocation").has_field("unused_leaves"):
frappe.reload_doc("HR", "doctype", "Leave Allocation")
update_leave_allocation_fieldname()
generate_allocation_ledger_entries()
generate_application_leave_ledger_entries()
generate_encashment_leave_ledger_entries()
generate_expiry_allocation_ledger_entries()
def update_leave_allocation_fieldname():
''' maps data from old field to the new field '''
frappe.db.sql("""
UPDATE `tabLeave Allocation`
SET `unused_leaves` = `carry_forwarded_leaves`
""")
def generate_allocation_ledger_entries():
''' fix ledger entries for missing leave allocation transaction '''
allocation_list = get_allocation_records()
for allocation in allocation_list:
if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name}):
allocation.update(dict(doctype="Leave Allocation"))
allocation_obj = frappe.get_doc(allocation)
allocation_obj.create_leave_ledger_entry()
def generate_application_leave_ledger_entries():
''' fix ledger entries for missing leave application transaction '''
leave_applications = get_leaves_application_records()
for application in leave_applications:
if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': application.name}):
application.update(dict(doctype="Leave Application"))
frappe.get_doc(application).create_leave_ledger_entry()
def generate_encashment_leave_ledger_entries():
''' fix ledger entries for missing leave encashment transaction '''
leave_encashments = get_leave_encashment_records()
for encashment in leave_encashments:
if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': encashment.name}):
encashment.update(dict(doctype="Leave Encashment"))
frappe.get_doc(encashment).create_leave_ledger_entry()
def generate_expiry_allocation_ledger_entries():
''' fix ledger entries for missing leave allocation transaction '''
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation
allocation_list = get_allocation_records()
for allocation in allocation_list:
if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name, 'is_expired': 1}):
allocation.update(dict(doctype="Leave Allocation"))
allocation_obj = frappe.get_doc(allocation)
if allocation_obj.to_date <= getdate(today()):
expire_allocation(allocation_obj)
def get_allocation_records():
return frappe.get_all("Leave Allocation", filters={
"docstatus": 1
}, fields=['name', 'employee', 'leave_type', 'new_leaves_allocated',
'unused_leaves', 'from_date', 'to_date', 'carry_forward'
], order_by='to_date ASC')
def get_leaves_application_records():
return frappe.get_all("Leave Application", filters={
"docstatus": 1
}, fields=['name', 'employee', 'leave_type', 'total_leave_days', 'from_date', 'to_date'])
def get_leave_encashment_records():
return frappe.get_all("Leave Encashment", filters={
"docstatus": 1
}, fields=['name', 'employee', 'leave_type', 'encashable_days', 'encashment_date'])

View File

@@ -5,67 +5,67 @@ from __future__ import unicode_literals
import frappe
parentfield = {
'item_code': 'items',
'item_group': 'item_groups',
'brand': 'brands'
'item_code': 'items',
'item_group': 'item_groups',
'brand': 'brands'
}
def execute():
if not frappe.get_all('Pricing Rule', limit=1):
return
if not frappe.get_all('Pricing Rule', limit=1):
return
frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail')
doctypes = {'Supplier Quotation': 'buying', 'Purchase Order': 'buying', 'Purchase Invoice': 'accounts',
'Purchase Receipt': 'stock', 'Quotation': 'selling', 'Sales Order': 'selling',
'Sales Invoice': 'accounts', 'Delivery Note': 'stock'}
frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail')
doctypes = {'Supplier Quotation': 'buying', 'Purchase Order': 'buying', 'Purchase Invoice': 'accounts',
'Purchase Receipt': 'stock', 'Quotation': 'selling', 'Sales Order': 'selling',
'Sales Invoice': 'accounts', 'Delivery Note': 'stock'}
for doctype, module in doctypes.items():
frappe.reload_doc(module, 'doctype', frappe.scrub(doctype))
for doctype, module in doctypes.items():
frappe.reload_doc(module, 'doctype', frappe.scrub(doctype))
child_doc = frappe.scrub(doctype) + '_item'
frappe.reload_doc(module, 'doctype', child_doc)
child_doc = frappe.scrub(doctype) + '_item'
frappe.reload_doc(module, 'doctype', child_doc, force=True)
child_doctype = doctype + ' Item'
child_doctype = doctype + ' Item'
frappe.db.sql(""" UPDATE `tab{child_doctype}` SET pricing_rules = pricing_rule
WHERE docstatus < 2 and pricing_rule is not null and pricing_rule != ''
""".format(child_doctype= child_doctype))
frappe.db.sql(""" UPDATE `tab{child_doctype}` SET pricing_rules = pricing_rule
WHERE docstatus < 2 and pricing_rule is not null and pricing_rule != ''
""".format(child_doctype= child_doctype))
data = frappe.db.sql(""" SELECT pricing_rule, name, parent,
parenttype, creation, modified, docstatus, modified_by, owner, name
FROM `tab{child_doc}` where docstatus < 2 and pricing_rule is not null
and pricing_rule != ''""".format(child_doc=child_doctype), as_dict=1)
data = frappe.db.sql(""" SELECT pricing_rule, name, parent,
parenttype, creation, modified, docstatus, modified_by, owner, name
FROM `tab{child_doc}` where docstatus < 2 and pricing_rule is not null
and pricing_rule != ''""".format(child_doc=child_doctype), as_dict=1)
values = []
for d in data:
values.append((d.pricing_rule, d.name, d.parent, 'pricing_rules', d.parenttype,
d.creation, d.modified, d.docstatus, d.modified_by, d.owner, frappe.generate_hash("", 10)))
values = []
for d in data:
values.append((d.pricing_rule, d.name, d.parent, 'pricing_rules', d.parenttype,
d.creation, d.modified, d.docstatus, d.modified_by, d.owner, frappe.generate_hash("", 10)))
if values:
frappe.db.sql(""" INSERT INTO
`tabPricing Rule Detail` (`pricing_rule`, `child_docname`, `parent`, `parentfield`, `parenttype`,
`creation`, `modified`, `docstatus`, `modified_by`, `owner`, `name`)
VALUES {values} """.format(values=', '.join(['%s'] * len(values))), tuple(values))
if values:
frappe.db.sql(""" INSERT INTO
`tabPricing Rule Detail` (`pricing_rule`, `child_docname`, `parent`, `parentfield`, `parenttype`,
`creation`, `modified`, `docstatus`, `modified_by`, `owner`, `name`)
VALUES {values} """.format(values=', '.join(['%s'] * len(values))), tuple(values))
frappe.reload_doc('accounts', 'doctype', 'pricing_rule')
frappe.reload_doc('accounts', 'doctype', 'pricing_rule')
for doctype, apply_on in {'Pricing Rule Item Code': 'Item Code',
'Pricing Rule Item Group': 'Item Group', 'Pricing Rule Brand': 'Brand'}.items():
frappe.reload_doc('accounts', 'doctype', frappe.scrub(doctype))
for doctype, apply_on in {'Pricing Rule Item Code': 'Item Code',
'Pricing Rule Item Group': 'Item Group', 'Pricing Rule Brand': 'Brand'}.items():
frappe.reload_doc('accounts', 'doctype', frappe.scrub(doctype))
field = frappe.scrub(apply_on)
data = frappe.get_all('Pricing Rule', fields=[field, "name", "creation", "modified",
"owner", "modified_by"], filters= {'apply_on': apply_on})
field = frappe.scrub(apply_on)
data = frappe.get_all('Pricing Rule', fields=[field, "name", "creation", "modified",
"owner", "modified_by"], filters= {'apply_on': apply_on})
values = []
for d in data:
values.append((d.get(field), d.name, parentfield.get(field), 'Pricing Rule',
d.creation, d.modified, d.owner, d.modified_by, frappe.generate_hash("", 10)))
values = []
for d in data:
values.append((d.get(field), d.name, parentfield.get(field), 'Pricing Rule',
d.creation, d.modified, d.owner, d.modified_by, frappe.generate_hash("", 10)))
if values:
frappe.db.sql(""" INSERT INTO
`tab{doctype}` ({field}, parent, parentfield, parenttype, creation, modified,
owner, modified_by, name)
VALUES {values} """.format(doctype=doctype,
field=field, values=', '.join(['%s'] * len(values))), tuple(values))
if values:
frappe.db.sql(""" INSERT INTO
`tab{doctype}` ({field}, parent, parentfield, parenttype, creation, modified,
owner, modified_by, name)
VALUES {values} """.format(doctype=doctype,
field=field, values=', '.join(['%s'] * len(values))), tuple(values))

View File

@@ -145,7 +145,7 @@ class Task(NestedSet):
def populate_depends_on(self):
if self.parent_task:
parent = frappe.get_cached_doc('Task', self.parent_task)
parent = frappe.get_doc('Task', self.parent_task)
if not self.name in [row.task for row in parent.depends_on]:
parent.append("depends_on", {
"doctype": "Task Depends On",
@@ -164,7 +164,7 @@ class Task(NestedSet):
if self.status not in ('Cancelled', 'Completed') and self.exp_end_date:
from datetime import datetime
if self.exp_end_date < datetime.now().date():
self.db_set('status', 'Overdue')
self.db_set('status', 'Overdue', update_modified=False)
self.update_project()
@frappe.whitelist()

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