Compare commits

..

149 Commits

Author SHA1 Message Date
Sahil Khan
98ddbf05b1 Merge branch 'v12-pre-release' into version-12 2019-12-20 15:55:12 +05:30
Sahil Khan
67d25028b2 bumped to version 12.3.0 2019-12-20 16:15:12 +05:50
Mangesh-Khairnar
328d5920bd fix(expense-claim): update status (#20033)
* fix(expense-claim): update status

* fix(expense-claim): compare using grandtotal precision
2019-12-20 15:26:49 +05:30
rohitwaghchaure
81d614b6bd Merge pull request #20007 from 0Pranav/appointment-schedulling-v12
fix: multiple issues with appointment schedulling
2019-12-20 15:17:41 +05:30
rohitwaghchaure
38540e85e8 Merge pull request #20029 from deepeshgarg007/gstr-2-fix-v12
fix: Tax amount not visible for some invoices
2019-12-20 13:35:14 +05:30
rohitwaghchaure
b3d97a560f Merge pull request #20027 from rohitwaghchaure/subcontracting_issue_for_partial_purchase_receipt_pre_release
fix: incorrect consumed qty for partial purchase receipt in subcontra…
2019-12-20 13:05:19 +05:30
Rohit Waghchaure
39436c6d38 fix: incorrect consumed qty for partial purchase receipt in subcontracting 2019-12-20 12:59:56 +05:30
deepeshgarg007
55bf951ff5 fix: Tax amount not visible for some invoices 2019-12-20 12:43:55 +05:30
0Pranav
220a208f4e fix: default timezone selection 2019-12-19 12:28:12 +05:30
0Pranav
0c8e46fdea fix: remove timezones in js 2019-12-19 12:25:29 +05:30
Deepesh Garg
ee4901f4a0 Merge pull request #19990 from 0Pranav/mapped-doc-for-customer-quotation-v12
fix: use open_mapped_doc instead of create_new_doc
2019-12-19 12:00:53 +05:30
rohitwaghchaure
5dd7503516 Merge pull request #19995 from rohitwaghchaure/fixed_pricing_rule_issue_for_product_discount_pre_relesae
fix: Pricing Rule Discount for Product
2019-12-19 11:11:37 +05:30
deepeshgarg007
5cc0e08a41 fix: Use get_value instead of get_doc and formatting 2019-12-19 11:07:43 +05:30
Rohit Waghchaure
fecf5a9a15 fix: Pricing Rule Discount for Product 2019-12-18 17:48:39 +05:30
0Pranav
5b4050a4ff add link to appointment booking in sidebar 2019-12-18 16:30:54 +05:30
0Pranav
4d7862ef4c fix: defualt timezone not getting selected 2019-12-18 16:27:16 +05:30
0Pranav
6e41475612 fix : only set price list if it exists for customer 2019-12-18 16:05:59 +05:30
sahil28297
803e0ec27c Merge pull request #19988 from rohitwaghchaure/change_log_for_v12_3_0
feat: v12_3_0 change log
2019-12-18 15:54:37 +05:30
Rohit Waghchaure
b3addff99e v12_3_0 change log 2019-12-18 15:49:32 +05:30
0Pranav
200ceb5352 use open_mapped_doc instead of create_new_doc 2019-12-18 12:34:19 +05:30
rohitwaghchaure
8c50f5c23f Merge pull request #19977 from Mangesh-Khairnar/fix-compensatory-off-pre
fix: compensatory off leave creation
2019-12-18 11:29:18 +05:30
Mangesh-Khairnar
86600ac8b9 fix: allow creation of additional leave ledger entry 2019-12-17 18:37:15 +05:30
Mangesh-Khairnar
b76a04b470 fix: compensatory leave request creation 2019-12-17 18:37:09 +05:30
rohitwaghchaure
80913994da Merge pull request #19974 from rohitwaghchaure/not_able_to_make_work_order_from_bom
fix: not able to make work order from BOM
2019-12-17 18:15:37 +05:30
Rohit Waghchaure
92ecdbe0c8 fix: not able to make work order from BOM 2019-12-17 18:13:54 +05:30
rohitwaghchaure
c920efc156 Merge pull request #19963 from rohitwaghchaure/allow_overproduction_against_work_order_version_12_hotfix
fix: not allow to over production against work order
2019-12-16 18:04:27 +05:30
rohitwaghchaure
3b9fe1ae6f Merge pull request #19959 from rohitwaghchaure/fixed_incorrect_child_bom_fecthed
fix: incorrect child boms fetched
2019-12-16 17:00:08 +05:30
Rohit Waghchaure
c76c5e699b fix: now allow to over production against work order 2019-12-16 16:59:01 +05:30
Rohit Waghchaure
666fba94e2 fix: incorrect children boms fetched 2019-12-16 16:18:33 +05:30
rohitwaghchaure
48a8a40703 Merge pull request #19944 from nextchamp-saqib/gl-precision-hotfix
fix: gl entries doesn't filter based on debit precision
2019-12-16 15:29:04 +05:30
rohitwaghchaure
5646816282 Merge pull request #19870 from nextchamp-saqib/website-hotfix
fix: website showing disabled items in product list
2019-12-16 15:25:07 +05:30
thefalconx33
f8df3c7af2 fix: review changes 2019-12-16 15:03:27 +05:30
rohitwaghchaure
62d4dfa883 Merge pull request #19956 from nextchamp-saqib/pos-serial-no
fix: display serial no selection on adding items to cart
2019-12-16 14:54:23 +05:30
thefalconx33
b8f9fd023b fix: display serial no selection on adding items to cart 2019-12-16 14:49:59 +05:30
rohitwaghchaure
0df3c93737 Merge pull request #19936 from benknowles/patch-3
fix: task validation error when adding tasks to projects
2019-12-16 14:01:20 +05:30
rohitwaghchaure
e0e7dcd2f6 Merge pull request #19914 from nextchamp-saqib/cart-address-hotfix
fix: enable adding of address without enabling checkout feature
2019-12-16 13:52:48 +05:30
rohitwaghchaure
d5b1baed39 Merge pull request #19907 from 0Pranav/appointment-schedulling-v12
fix: change book-appointment route
2019-12-16 13:50:47 +05:30
rohitwaghchaure
800545ff5b Merge pull request #19947 from rohitwaghchaure/pricing_rule_not_working_for_product_discount_v12_hotfix
fix: pricing rule not working for product discount
2019-12-16 13:37:45 +05:30
rohitwaghchaure
388a177f75 Merge pull request #19939 from marination/item_manufacturer_table
fix(ui): Removed 'manufacturers' table from Item Master
2019-12-16 13:36:56 +05:30
Rohit Waghchaure
821166c628 fix: schedule date 2019-12-16 12:29:39 +05:30
marination
2b8df06f8e fix: Removed validation from non existent manufacturers table 2019-12-16 12:18:24 +05:30
Rohit Waghchaure
4e8e466a98 fix: pricing rule not working for production discount 2019-12-16 11:16:36 +05:30
Deepesh Garg
31d4482336 Merge pull request #19953 from surajshetty3416/fix-profit-and-loss-statement-version-12-hotfix
fix: Profit and Lost (financial statement) report
2019-12-15 20:23:05 +05:30
Suraj Shetty
5cd8c7c722 fix: Financial statement report
- Hidden column should note be considered in the report
- Remove hardcoded currency formatting
- Remove duplicate letterhead in the report
(print_template already adds one)
- Remove extra quotes from Total Amount text
2019-12-14 23:33:14 +05:30
Deepesh Garg
e14d9b5476 Merge pull request #19951 from deepeshgarg007/patch-and-address-fix-v12
fix: Add missing import
2019-12-14 23:03:27 +05:30
deepeshgarg007
6a8ff1bebe fix: Add missing import 2019-12-14 21:31:11 +05:30
rohitwaghchaure
a41d464198 Merge pull request #19942 from deepeshgarg007/pricing_rule_fix_v12
fix: Price rule filtering fix
2019-12-13 16:01:35 +05:30
thefalconx33
980793bde0 fix: gl entries doesn't filter based on debit precision 2019-12-13 15:42:34 +05:30
deepeshgarg007
b7329eac19 fix: Price rule filtering fix 2019-12-13 13:49:12 +05:30
marination
9ec5cb2570 fix: Removed 'manufacturers' table from Item Master 2019-12-13 13:12:10 +05:30
Deepesh Garg
44296a392d Merge pull request #19735 from marination/zero-division-v12-hotfix
fix: Division by zero error in Stock Entry
2019-12-13 09:24:31 +05:30
Ben Knowles
9097c7e11c fix: task validation error when adding tasks to projects
Related to PR #19919
2019-12-12 11:30:17 -06:00
Deepesh Garg
0256d7549c Merge pull request #19931 from deepeshgarg007/regional_address_fix_v12
fix: Get regional address details
2019-12-12 16:54:34 +05:30
marination
94d8b99ef9 fix: Distribute charges based on quantity if Total Basic Amount is Zero. 2019-12-12 16:51:11 +05:30
Deepesh Garg
3fe1335f7b Merge pull request #19926 from prssanna/file-upload-fix-v12
fix: Bank statement not getting attached in Bank Reconciliation
2019-12-12 15:19:36 +05:30
Deepesh Garg
dc7a4ac8af Merge pull request #19925 from rohitwaghchaure/not_able_to_submit_the_landed_cost_voucher_version_12
fix: not able to submit the landed cost voucher
2019-12-12 15:17:05 +05:30
Deepesh Garg
c0ff769214 Merge pull request #19924 from rohitwaghchaure/not_able_to_submit_the_landed_cost_voucher_version_12_hotfix
fix: not able to submit the landed cost voucher
2019-12-12 15:16:31 +05:30
deepeshgarg007
0a527b9f9a fix: Get regional address details fix 2019-12-12 15:13:30 +05:30
prssanna
e03871f9de fix: empty fname and fcontent of uploaded file 2019-12-12 12:22:03 +05:30
Rohit Waghchaure
c0286780bd fix: not able to submit the landed cost voucher 2019-12-12 12:14:26 +05:30
Rohit Waghchaure
9cc484650b fix: not able to submit the landed cost voucher 2019-12-12 12:13:40 +05:30
rohitwaghchaure
319f126258 Merge pull request #19901 from rohitwaghchaure/not_able_to_cancel_landed_cost_voucher_v12
fix: not able to cancel the landed cost voucher
2019-12-12 12:11:02 +05:30
0Pranav
234de12836 fix: add init files for book-appointments 2019-12-12 11:20:31 +05:30
rohitwaghchaure
4c19000ed9 Merge pull request #19918 from rohitwaghchaure/fixed_pricing_rule_working_on_other_items_v12_cherry_pick
fix: pricing rule not working
2019-12-12 10:20:05 +05:30
Deepesh Garg
a1651ca5f2 Merge pull request #19898 from hrwX/qms-int-fix-v12
fix: rename labels
2019-12-12 08:45:20 +05:30
Rohit Waghchaure
4d042cd81a Merge branch 'version-12' into fixed_pricing_rule_working_on_other_items_v12_cherry_pick 2019-12-11 22:53:27 +05:30
rohitwaghchaure
d72fae670a Merge pull request #19916 from rohitwaghchaure/fixed_pricing_rule_working_on_other_items
fix: pricing rule working on non pricing rule items
2019-12-11 22:18:07 +05:30
Rohit Waghchaure
d458e25dc5 fix: pricing rule working on non pricing rule items 2019-12-11 21:55:28 +05:30
Deepesh Garg
43474a3afa Merge pull request #19896 from hrwX/project-fix_v12
fix: Set project in child table via dashboard
2019-12-11 18:43:29 +05:30
Deepesh Garg
7daa2a2085 Merge pull request #19894 from marination/rounded_total_v12_hotfix
fix: Disable Rounded Total always showing field default value
2019-12-11 18:41:25 +05:30
Deepesh Garg
45075d8915 Merge pull request #19900 from rohitwaghchaure/not_able_to_cancel_landed_cost_voucher_v12_hotfix
fix: not able to cancel the landed cost voucher
2019-12-11 18:37:34 +05:30
thefalconx33
1de3040ecb fix: additional notes from Quotations not saved in SO 2019-12-11 18:11:48 +05:30
thefalconx33
af10f659d9 fix: enable address without checkout feature
* fix add address form country link field
2019-12-11 17:44:08 +05:30
thefalconx33
6f36691c64 fix: handle scenario with no condition 2019-12-11 16:10:56 +05:30
0Pranav
dfe629aff7 fix: change book-appointment route 2019-12-11 15:18:59 +05:30
Rohit Waghchaure
23bf2a6647 fix: not able to cancel the landed cost voucher 2019-12-11 13:52:47 +05:30
Rohit Waghchaure
b69cb8080c fix: not able to cancel the landed cost voucher 2019-12-11 13:52:18 +05:30
Himanshu Warekar
f23b5ed23b fix: rename labels 2019-12-11 13:10:31 +05:30
Himanshu Warekar
0a28387c70 fix: set project 2019-12-11 12:49:43 +05:30
marination
caae8c57bc fix: Disable Rounded Total always showing field default value 2019-12-11 12:18:51 +05:30
Nabin Hait
44ae135c36 Merge branch 'version-12' into version-12-hotfix 2019-12-11 09:12:10 +05:30
Deepesh Garg
47e786ef62 fix: Rounding Adjustment GL entry fix (#19839)
* fix: Rounding Adjustment GL entry fix

* fix: Spacing in tab

* fix: Comment fix
2019-12-11 09:06:37 +05:30
Deepesh Garg
f10be395c1 fix: NoneType' object has no attribute '__getitem_'_ (#19860) 2019-12-11 09:06:25 +05:30
rohitwaghchaure
ac967d09ec fix: Item-wise Sales History report not working (#19890) 2019-12-10 21:34:20 +05:30
Saqib
d1e8e8652f fix: incorrect account mapping for child companies (#19888)
* fix: incorrect account mapping for child companies on adding account to parent company

* Update account.py
2019-12-10 21:32:57 +05:30
Deepesh Garg
72649c207f feat(regional): Auto state wise taxation for GST India (#19877)
* feat(regional): Auto state wise taxation for GST India

* fix: Update gst category on addition of GSTIN

* fix: Codacy and travis fixes

* fix: Travis

* fix(test): Update GST category only if GSTIN field available

* fix: Test Cases

* fix: Do not skip accounts if place of supply is not present

* fix: Auto GST taxation for SEZ Party types

* fix: Automatic taxation for multi state

* fix: Codacy and travis fixes

* fix: Auto GST template selection in Sales Order

* fix: Move inter state check and source state to tax category

* fix: Remove unique check from tax template

* fix: Remove unique check from tax template

* fix: Address fetching logic in Sales

* fix: fecth tax template on company address change

* fix: fetch company gstin on address change

* fix: company_gstin set value fix

* fix: Mutiple fixes and code refactor

* fix: Add missing semicolon

* fix: Company address fetching in sales invoice

* fix: Remove print statement

* fix: Import functools

* fix: Naming fixes and code cleanup

* fix: Update patches

* fix: Remove changes in patches.txt

* fix: Iteritems compatibility for python 3
2019-12-10 15:54:29 +05:30
Deepesh Garg
d06b685fdf fix: Append expense account only if expense account exists (#19881) 2019-12-10 12:15:06 +05:30
marination
6411a56cdc fix: Changed check condition and added test 2019-12-09 21:36:02 +05:30
Saqib
34b3b04fb0 fix: error message displays asset category as None (#19874)
* fix: error message displays asset category as None

* fix: asset gl_entries doesn't considers asset category's cwip account
2019-12-09 19:06:14 +05:30
Deepesh Garg
b1a2a16f43 Merge pull request #19868 from nextchamp-saqib/report-col-hotfix
fix: column data not visible after manual selection of columns
2019-12-09 17:57:41 +05:30
thefalconx33
f092e68a58 fix: website showing disabled items in product list 2019-12-09 17:03:32 +05:30
thefalconx33
6d497ccb4c fix: column data not visible after manual selection of columns 2019-12-09 15:24:39 +05:30
Deepesh Garg
a7b97f7bac Merge pull request #19867 from nextchamp-saqib/cart-fix-hotfix
fix: Error while placing order of cart items added yesterday
2019-12-09 15:23:04 +05:30
thefalconx33
f40d3bd10f fix: due date before posting date for items added to cart yesterday 2019-12-09 14:07:25 +05:30
Deepesh Garg
1e2be32860 fix: Consistency in button positions in Sales Order and Purchase Order (#19834) 2019-12-09 13:04:58 +05:30
Deepesh Garg
6aec9e32d4 fix: Rounding Adjustment GL entry fix (#19839)
* fix: Rounding Adjustment GL entry fix

* fix: Spacing in tab

* fix: Comment fix
2019-12-09 13:03:02 +05:30
Deepesh Garg
59cc0e5029 fix: NoneType' object has no attribute '__getitem_'_ (#19860) 2019-12-09 11:28:35 +05:30
Deepesh Garg
851f39cee1 Merge pull request #19837 from deepeshgarg007/gst_1_validation_msg_v12
fix: Validation msg fix in GSTR-1 report
2019-12-07 19:53:03 +05:30
rohitwaghchaure
6822a30f8c Merge pull request #19850 from rohitwaghchaure/fixed_timsheet_overlap_issue_v12_hotfix
fix: timesheet overlap error
2019-12-07 14:10:54 +05:30
Rohit Waghchaure
495ba1618b fix: timsheet overlap error 2019-12-07 13:22:08 +05:30
deepeshgarg007
778d7595aa fix: Add missing semicolon 2019-12-06 20:00:56 +05:30
deepeshgarg007
a40dbd0384 fix: Validation msg fix in GSTR-1 report 2019-12-06 19:46:32 +05:30
Deepesh Garg
80dfb9f834 Merge pull request #19835 from deepeshgarg007/accounts_payable_terms_v12
feat: Accounts Payable report based on payment terms
2019-12-06 19:41:27 +05:30
deepeshgarg007
dabb303358 feat: Accounts Payable report based on payment terms 2019-12-06 17:52:48 +05:30
Pranav Nachnekar
d16ef54665 fix: query for finding lost quotation (#19801)
* fix:query for finding lost quotation

* Update opportunity.py
2019-12-04 15:31:25 +05:30
Nabin Hait
dc248b9458 optimize: Optimization of Receivable report filtered based on sales person (#19797) 2019-12-04 15:30:39 +05:30
Nabin Hait
bf0f26b4a4 fix: Service start and end date validation for deferred accounting (#19806) 2019-12-04 15:29:54 +05:30
Mangesh-Khairnar
929fd4ce47 enhancement(fixed-asset-register): add date filter (#19804)
* feat: add date filter in the fixed asset register

* fix: remove function from keyword argument
2019-12-04 14:10:41 +05:30
Deepesh Garg
81c895b21e Merge pull request #19793 from nabinhait/ar-summary-based-on-terms-v12
feat: Receivable / payable summary based on payment terms
2019-12-04 11:23:31 +05:30
Shivam Mishra
27a21f80d7 feat: allow searching from meta fields (#19725)
* feat: allow searching from meta fields

* feat: remove description in query based on number of items
2019-12-03 17:26:50 +05:30
sahil28297
aa7085e11c fix(patch): set proper tax_type based on company and set proper account if not already present (#19788) 2019-12-03 17:07:26 +05:30
Nabin Hait
6e5363ba48 feat: Receivable / payable summary based on payment terms 2019-12-03 16:58:02 +05:30
Deepesh Garg
53746636c3 fix: Party name field in trial balacne for party report (#19790) 2019-12-03 16:30:09 +05:30
Deepesh Garg
485d48c101 fix: Unable to see parties with negative balance in AR/AP Summary (#19777) 2019-12-03 15:12:28 +05:30
Marica
0e1ef35968 fix: Item qty cannot be zero in Purchase Receipt (#19780) 2019-12-03 12:59:15 +05:30
gavin
35effe9be0 fix: AttributeError on new Student creation (#19787) 2019-12-03 12:54:18 +05:30
Shivam Mishra
648d6e46f3 fix: query for item group listing (#19785) 2019-12-03 12:52:58 +05:30
Nabin Hait
d6d9a3ddd7 Update employee.py 2019-12-03 12:52:12 +05:30
Deepesh Garg
18f05db19a Merge pull request #19763 from Mangesh-Khairnar/fix-pr-creation-so
fix(sales-order): allow payment request creation for so that are not billed
2019-12-03 10:42:53 +05:30
Rucha Mahabal
586fecfe73 fix: render_template for subject in Email Campaign (#19771) 2019-12-02 16:25:29 +05:30
sahil28297
14018b3dea bumped to version 12.2.2 2019-12-02 13:05:27 +05:30
Deepesh Garg
1c196f958f Merge pull request #19768 from deepeshgarg007/avaiable_stock_for_v12
fix: Available stock for packing item report
2019-12-02 11:57:45 +05:30
rohitwaghchaure
91f2cfb999 Merge pull request #19769 from rohitwaghchaure/sales_invoice_none_type_error_serial_no_validation
fix: Serial no validation against sales invoice
2019-12-02 09:46:23 +05:30
deepeshgarg007
c0a0331570 fix: Validation msg 2019-12-02 09:43:11 +05:30
deepeshgarg007
4ceba43e43 fix: Serial no validation against sales invoice 2019-12-02 09:43:04 +05:30
deepeshgarg007
9b64e2e24c fix: Available stock for packing item report 2019-12-01 22:20:18 +05:30
Deepesh Garg
da5e227ad6 fix: Post GL entry fix for asset (#19752) 2019-12-01 10:06:31 +05:30
Mangesh-Khairnar
4f95e5d092 fix: show create payment request for so that are not billed 2019-11-30 20:31:18 +05:30
Deepesh Garg
6a8fd0102f fix: Serial no validation against sales invoice (#19749)
* fix: Serial no validation against sales invoice

* fix: Validation msg
2019-11-29 18:48:30 +05:30
Suraj Shetty
2b172ec4b4 fix: valuation of "finished good" item in purchase receipt (#19745)
* fix: Remove redundant purchase orders and unwanted condition

* fix: [WIP] Purchase receipt value

* fix: Add raw material cost based on transfered raw material

* fix: get_qty_to_be_received

* fix: Remove debugger statement

* fix: Reset rm_supp_cost before setting subcontracted raw_materials

* test: Fix and modify tests for backflush_based_on_stock_entry

* fix: Add non stock items to Purchase Receipt from Purchase Order

* fix: Ignore valuation rate check for non stock raw material

* fix: Rename check all rows

* fix: Remove amount from test

* test: Fix item rate error

* fix: handling of serial nos in backflush

* fix: Add serial no. of raw materials

* fix: [WIP] Handle Batch nos for purchase reciept backflushed raw material

* fix: Raw material batch number selection in purchase receipt

* Update test_purchase_order.py
2019-11-29 16:59:21 +05:30
Marica
5d2ad7fc38 fix: UOM was not fetching in purchase invoice (#19732) (#19737)
* fix: UOM was not fetching in purchase invoice

* fix: Changes requested

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2019-11-28 20:00:51 +05:30
rohitwaghchaure
3347473aa1 fix: removed stock value and account balance out of sync validation (#19728) 2019-11-28 20:00:41 +05:30
Rohit Waghchaure
7f951b5595 fix: revert value out of sync feature 2019-11-28 20:00:33 +05:30
Marica
208c69f196 fix: Permission issue in Stock Entry (#19739) 2019-11-28 19:39:55 +05:30
Marica
32b69bf122 fix: UOM was not fetching in purchase invoice (#19732) (#19737)
* fix: UOM was not fetching in purchase invoice

* fix: Changes requested

Co-authored-by: Marica <maricadsouza221197@gmail.com>
2019-11-28 19:03:14 +05:30
Marica
b1fac1817c fix: Validation for Suppliers in SO to PO (#19700)
- Check if there is a Supplier against atleast one item in Sales Order
- Validation message earlier was vague
2019-11-28 18:23:11 +05:30
Marica
6516358a71 fix: Changed type of column 'serial_no' in Stock Ledger Entry (#19704) 2019-11-28 18:20:53 +05:30
marination
c6e2087673 fix: Division by zero error in Stock Entry 2019-11-28 16:54:58 +05:30
Shivam Mishra
d8469a7bfa fix: handle None case for get_shipping_amount_from_rules (#19724) 2019-11-28 16:47:14 +05:30
Marica
cf645aceae chore: Added Quick Stock Balance to Stock Module (#19727)
- Also 'Stock Balance Report' button no longer primary button
2019-11-28 16:44:56 +05:30
rohitwaghchaure
3dd72e238f fix: removed stock value and account balance out of sync validation (#19728) 2019-11-28 16:44:05 +05:30
Deepesh Garg
b74ce74ec9 Merge pull request #19718 from deepeshgarg007/status_fix_v12
fix: Path for quotation expiry method in hooks
2019-11-28 12:24:20 +05:30
deepeshgarg007
074aaa6005 fix: Path for quotation expiry method in hooks 2019-11-28 10:31:11 +05:30
Marica
9d5f43f4f0 fix: get_batch_qty_and_serial_no() requires argument 'stock_qty' (#19694) 2019-11-27 15:50:45 +05:30
rohitwaghchaure
7522aadc6e Merge pull request #19697 from rohitwaghchaure/dont_stop_submitting_entry_due_to_mismatch_issue
fix: revert value out of sync feature
2019-11-27 12:32:04 +05:30
rohitwaghchaure
326fdcb454 Merge pull request #19687 from deepeshgarg007/sales_invoice_fix_develop
fix: Serial no validation against sales invoice
2019-11-27 11:36:10 +05:30
Rohit Waghchaure
c41addec96 fix: revert value out of sync feature 2019-11-27 08:49:08 +05:30
deepeshgarg007
defed15528 fix: Validation msg 2019-11-26 16:12:29 +05:30
deepeshgarg007
cbc29989fe fix: Serial no validation against sales invoice 2019-11-26 15:13:23 +05:30
5843 changed files with 708517 additions and 1089758 deletions

View File

@@ -1,14 +0,0 @@
# Root editor config file
root = true
# Common settings
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
# python, js indentation settings
[{*.py,*.js}]
indent_style = tab
indent_size = 4

View File

@@ -5,7 +5,7 @@
"es6": true "es6": true
}, },
"parserOptions": { "parserOptions": {
"ecmaVersion": 9, "ecmaVersion": 6,
"sourceType": "module" "sourceType": "module"
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
@@ -15,14 +15,6 @@
"tab", "tab",
{ "SwitchCase": 1 } { "SwitchCase": 1 }
], ],
"brace-style": [
"error",
"1tbs"
],
"space-unary-ops": [
"error",
{ "words": true }
],
"linebreak-style": [ "linebreak-style": [
"error", "error",
"unix" "unix"
@@ -52,10 +44,12 @@
"no-control-regex": [ "no-control-regex": [
"off" "off"
], ],
"space-before-blocks": "warn", "spaced-comment": [
"keyword-spacing": "warn", "warn"
"comma-spacing": "warn", ],
"key-spacing": "warn" "no-trailing-spaces": [
"warn"
]
}, },
"root": true, "root": true,
"globals": { "globals": {
@@ -92,7 +86,6 @@
"cur_page": true, "cur_page": true,
"cur_list": true, "cur_list": true,
"cur_tree": true, "cur_tree": true,
"cur_pos": true,
"msg_dialog": true, "msg_dialog": true,
"is_null": true, "is_null": true,
"in_list": true, "in_list": true,
@@ -147,15 +140,9 @@
"Chart": true, "Chart": true,
"Cypress": true, "Cypress": true,
"cy": true, "cy": true,
"describe": true,
"expect": true,
"it": true, "it": true,
"context": true, "context": true,
"before": true, "before": true,
"beforeEach": true, "beforeEach": true
"onScan": true,
"html2canvas": true,
"extend_cscript": true,
"localforage": true
} }
} }

37
.flake8
View File

@@ -1,37 +0,0 @@
[flake8]
ignore =
E121,
E126,
E127,
E128,
E203,
E225,
E226,
E231,
E241,
E251,
E261,
E265,
E302,
E303,
E305,
E402,
E501,
E741,
W291,
W292,
W293,
W391,
W503,
W504,
F403,
B007,
B950,
W191,
E124, # closing bracket, irritating while writing QB code
E131, # continuation line unaligned for hanging indent
E123, # closing bracket does not match indentation of opening bracket's line
E101, # ensured by use of black
max-line-length = 200
exclude=.github/helper/semgrep_rules

View File

@@ -1,22 +0,0 @@
# Since version 2.23 (released in August 2019), git-blame has a feature
# to ignore or bypass certain commits.
#
# This file contains a list of commits that are not likely what you
# are looking for in a blame, such as mass reformatting or renaming.
# You can set this file as a default ignore file for blame by running
# the following command.
#
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
# This commit just changes spaces to tabs for indentation in some files
5f473611bd6ed57703716244a054d3fb5ba9cd23
# Whitespace trimming throughout codebase
9bb69e711a5da43aaf8c8ecb5601aeffd89dbe5a
f0bcb753fb7ebbb64bb0d6906d431d002f0f7d8f
# imports cleanup
4b2be2999f2203493b49bf74c5b440d49e38b5e3
# formatting with black
c07713b860505211db2af685e2e950bf5dd7dd3a

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Community Forum
url: https://discuss.erpnext.com/
about: For general QnA, discussions and community help.

View File

@@ -8,7 +8,7 @@ Please post on our forums:
for questions about using `ERPNext`: https://discuss.erpnext.com for questions about using `ERPNext`: https://discuss.erpnext.com
for questions about using the `Frappe Framework`: ~~https://discuss.frappe.io~~ => [stackoverflow](https://stackoverflow.com/questions/tagged/frappe) tagged under `frappe` for questions about using the `Frappe Framework`: https://discuss.frappe.io
for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench) for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench)

View File

@@ -1,73 +0,0 @@
[flake8]
ignore =
B007,
B009,
B010,
B950,
E101,
E111,
E114,
E116,
E117,
E121,
E122,
E123,
E124,
E125,
E126,
E127,
E128,
E131,
E201,
E202,
E203,
E211,
E221,
E222,
E223,
E224,
E225,
E226,
E228,
E231,
E241,
E242,
E251,
E261,
E262,
E265,
E266,
E271,
E272,
E273,
E274,
E301,
E302,
E303,
E305,
E306,
E402,
E501,
E502,
E701,
E702,
E703,
E741,
F403,
W191,
W291,
W292,
W293,
W391,
W503,
W504,
E711,
E129,
F841,
E713,
E712,
B023
max-line-length = 200
exclude=.github/helper/semgrep_rules,test_*.py

View File

@@ -1,54 +0,0 @@
import sys
from urllib.parse import urlparse
import requests
docs_repos = [
"frappe_docs",
"erpnext_documentation",
"erpnext_com",
"frappe_io",
]
def uri_validator(x):
result = urlparse(x)
return all([result.scheme, result.netloc, result.path])
def docs_link_exists(body):
for line in body.splitlines():
for word in line.split():
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
if parsed_url.netloc == "github.com":
parts = parsed_url.path.split('/')
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True
elif parsed_url.netloc == "docs.erpnext.com":
return True
if __name__ == "__main__":
pr = sys.argv[1]
response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr))
if response.ok:
payload = response.json()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (title.startswith("feat")
and head_sha
and "no-docs" not in body
and "backport" not in body
):
if docs_link_exists(body):
print("Documentation Link Found. You're Awesome! 🎉")
else:
print("Documentation Link Not Found! ⚠️")
sys.exit(1)
else:
print("Skipping documentation checks... 🏃")

View File

@@ -1,48 +0,0 @@
#!/bin/bash
set -e
# Check for merge conflicts before proceeding
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
cd ~ || exit
sudo apt update && sudo apt install redis-server libcups2-dev
pip install frappe-bench
git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
mkdir ~/frappe-bench/sites/test_site
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
sudo chmod o+x /usr/local/bin/wkhtmltopdf
cd ~/frappe-bench || exit
sed -i 's/watch:/# watch:/g' Procfile
sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
bench get-app erpnext "${GITHUB_WORKSPACE}"
bench start &> bench_run_logs.txt &
bench --site test_site reinstall --yes

View File

@@ -1,63 +0,0 @@
import frappe
from frappe import _
from frappe.model.document import Document
# ruleid: frappe-modifying-but-not-comitting
def on_submit(self):
if self.value_of_goods == 0:
frappe.throw(_('Value of goods cannot be 0'))
self.status = 'Submitted'
# ok: frappe-modifying-but-not-comitting
def on_submit(self):
if self.value_of_goods == 0:
frappe.throw(_('Value of goods cannot be 0'))
self.status = 'Submitted'
self.db_set('status', 'Submitted')
# ok: frappe-modifying-but-not-comitting
def on_submit(self):
if self.value_of_goods == 0:
frappe.throw(_('Value of goods cannot be 0'))
x = "y"
self.status = x
self.db_set('status', x)
# ok: frappe-modifying-but-not-comitting
def on_submit(self):
x = "y"
self.status = x
self.save()
# ruleid: frappe-modifying-but-not-comitting-other-method
class DoctypeClass(Document):
def on_submit(self):
self.good_method()
self.tainted_method()
def tainted_method(self):
self.status = "uptate"
# ok: frappe-modifying-but-not-comitting-other-method
class DoctypeClass(Document):
def on_submit(self):
self.good_method()
self.tainted_method()
def tainted_method(self):
self.status = "update"
self.db_set("status", "update")
# ok: frappe-modifying-but-not-comitting-other-method
class DoctypeClass(Document):
def on_submit(self):
self.good_method()
self.tainted_method()
self.save()
def tainted_method(self):
self.status = "uptate"

View File

@@ -1,151 +0,0 @@
# This file specifies rules for correctness according to how frappe doctype data model works.
rules:
- id: frappe-modifying-but-not-comitting
patterns:
- pattern: |
def $METHOD(self, ...):
...
self.$ATTR = ...
- pattern-not: |
def $METHOD(self, ...):
...
self.$ATTR = ...
...
self.db_set(..., self.$ATTR, ...)
- pattern-not: |
def $METHOD(self, ...):
...
self.$ATTR = $SOME_VAR
...
self.db_set(..., $SOME_VAR, ...)
- pattern-not: |
def $METHOD(self, ...):
...
self.$ATTR = $SOME_VAR
...
self.save()
- metavariable-regex:
metavariable: '$ATTR'
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
- metavariable-regex:
metavariable: "$METHOD"
regex: "(on_submit|on_cancel)"
message: |
DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database.
languages: [python]
severity: ERROR
- id: frappe-modifying-but-not-comitting-other-method
patterns:
- pattern: |
class $DOCTYPE(...):
def $METHOD(self, ...):
...
self.$ANOTHER_METHOD()
...
def $ANOTHER_METHOD(self, ...):
...
self.$ATTR = ...
- pattern-not: |
class $DOCTYPE(...):
def $METHOD(self, ...):
...
self.$ANOTHER_METHOD()
...
def $ANOTHER_METHOD(self, ...):
...
self.$ATTR = ...
...
self.db_set(..., self.$ATTR, ...)
- pattern-not: |
class $DOCTYPE(...):
def $METHOD(self, ...):
...
self.$ANOTHER_METHOD()
...
def $ANOTHER_METHOD(self, ...):
...
self.$ATTR = $SOME_VAR
...
self.db_set(..., $SOME_VAR, ...)
- pattern-not: |
class $DOCTYPE(...):
def $METHOD(self, ...):
...
self.$ANOTHER_METHOD()
...
self.save()
def $ANOTHER_METHOD(self, ...):
...
self.$ATTR = ...
- metavariable-regex:
metavariable: "$METHOD"
regex: "(on_submit|on_cancel)"
message: |
self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database.
languages: [python]
severity: ERROR
- id: frappe-print-function-in-doctypes
pattern: print(...)
message: |
Did you mean to leave this print statement in? Consider using msgprint or logger instead of print statement.
languages: [python]
severity: WARNING
paths:
include:
- "*/**/doctype/*"
- id: frappe-modifying-child-tables-while-iterating
pattern-either:
- pattern: |
for $ROW in self.$TABLE:
...
self.remove(...)
- pattern: |
for $ROW in self.$TABLE:
...
self.append(...)
message: |
Child table being modified while iterating on it.
languages: [python]
severity: ERROR
paths:
include:
- "*/**/doctype/*"
- id: frappe-same-key-assigned-twice
pattern-either:
- pattern: |
{..., $X: $A, ..., $X: $B, ...}
- pattern: |
dict(..., ($X, $A), ..., ($X, $B), ...)
- pattern: |
_dict(..., ($X, $A), ..., ($X, $B), ...)
message: |
key `$X` is uselessly assigned twice. This could be a potential bug.
languages: [python]
severity: ERROR
- id: frappe-manual-commit
patterns:
- pattern: frappe.db.commit()
- pattern-not-inside: |
try:
...
except ...:
...
message: |
Manually commiting a transaction is highly discouraged. Read about the transaction model implemented by Frappe Framework before adding manual commits: https://frappeframework.com/docs/user/en/api/database#database-transaction-model If you think manual commit is required then add a comment explaining why and `// nosemgrep` on the same line.
paths:
exclude:
- "**/patches/**"
- "**/demo/**"
languages: [python]
severity: ERROR

View File

@@ -1,14 +0,0 @@
from frappe import _
# ruleid: frappe-missing-translate-function-in-report-python
{"label": "Field Label"}
# ruleid: frappe-missing-translate-function-in-report-python
dict(label="Field Label")
# ok: frappe-missing-translate-function-in-report-python
{"label": _("Field Label")}
# ok: frappe-missing-translate-function-in-report-python
dict(label=_("Field Label"))

View File

@@ -1,30 +0,0 @@
import frappe
from frappe import _, msgprint, throw
# ruleid: frappe-missing-translate-function-python
throw("Error Occured")
# ruleid: frappe-missing-translate-function-python
frappe.throw("Error Occured")
# ruleid: frappe-missing-translate-function-python
frappe.msgprint("Useful message")
# ruleid: frappe-missing-translate-function-python
msgprint("Useful message")
# ok: frappe-missing-translate-function-python
translatedmessage = _("Hello")
# ok: frappe-missing-translate-function-python
throw(translatedmessage)
# ok: frappe-missing-translate-function-python
msgprint(translatedmessage)
# ok: frappe-missing-translate-function-python
msgprint(_("Helpful message"))
# ok: frappe-missing-translate-function-python
frappe.throw(_("Error occured"))

View File

@@ -1,60 +0,0 @@
import re
import sys
errors_encounter = 0
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
f_string_pattern = re.compile(r"_\(f[\"']")
starts_with_f_pattern = re.compile(r"_\(f")
# skip first argument
files = sys.argv[1:]
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))]
for _file in files_to_scan:
with open(_file, 'r') as f:
print(f'Checking: {_file}')
file_lines = f.readlines()
for line_number, line in enumerate(file_lines, 1):
if 'frappe-lint: disable-translate' in line:
continue
start_matches = start_pattern.search(line)
if start_matches:
starts_with_f = starts_with_f_pattern.search(line)
if starts_with_f:
has_f_string = f_string_pattern.search(line)
if has_f_string:
errors_encounter += 1
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
continue
else:
continue
match = pattern.search(line)
error_found = False
if not match and line.endswith((',\n', '[\n')):
# concat remaining text to validate multiline pattern
line = "".join(file_lines[line_number - 1:])
line = line[start_matches.start() + 1:]
match = pattern.match(line)
if not match:
error_found = True
print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}')
if not error_found and not words_pattern.search(line):
error_found = True
print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}')
if error_found:
errors_encounter += 1
if errors_encounter > 0:
print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.')
sys.exit(1)
else:
print('\nGood To Go!')

View File

@@ -1,26 +0,0 @@
name: Backport
on:
pull_request_target:
types:
- closed
- labeled
jobs:
main:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout Actions
uses: actions/checkout@v2
with:
repository: "frappe/backport"
path: ./actions
ref: develop
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run backport
uses: ./actions/backport
with:
token: ${{secrets.BACKPORT_BOT_TOKEN}}
labelsToAdd: "backport"
title: "{{originalTitle}}"

View File

@@ -1,14 +0,0 @@
name: Trigger Docker build on release
on:
release:
types: [released]
jobs:
curl:
runs-on: ubuntu-latest
container:
image: alpine:latest
steps:
- name: curl
run: |
apk add curl bash
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}'

View File

@@ -1,25 +0,0 @@
name: 'Documentation Required'
on:
pull_request:
types: [ opened, synchronize, reopened, edited ]
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: 'Setup Environment'
uses: actions/setup-python@v2
with:
python-version: 3.6
- name: 'Clone repo'
uses: actions/checkout@v2
- name: Validate Docs
env:
PR_NUMBER: ${{ github.event.number }}
run: |
pip install requests --quiet
python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER

View File

@@ -1,31 +0,0 @@
name: Linters
on:
pull_request: { }
jobs:
linters:
name: linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install and Run Pre-commit
uses: pre-commit/action@v2.0.3
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- uses: returntocorp/semgrep-action@v1
env:
SEMGREP_TIMEOUT: 120
with:
config: >-
r/python.lang.correctness
./frappe-semgrep-rules/rules

View File

@@ -1,90 +0,0 @@
name: Patch
on:
pull_request:
paths-ignore:
- '**.js'
- '**.md'
workflow_dispatch:
concurrency:
group: patch-mariadb-v13-${{ github.event.number }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-18.04
timeout-minutes: 60
name: Patch Test
services:
mysql:
image: mariadb:10.3
env:
MYSQL_ALLOW_EMPTY_PASSWORD: YES
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 12
check-latest: true
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- name: Run Patch Tests
run: |
cd ~/frappe-bench/
wget https://erpnext.com/files/v10-erpnext.sql.gz
bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz
bench --site test_site migrate

View File

@@ -1,31 +0,0 @@
name: Generate Semantic Release
on:
push:
branches:
- version-13
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Entire Repository
uses: actions/checkout@v2
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js v14
uses: actions/setup-node@v2
with:
node-version: 14
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
- name: Create Release
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GIT_AUTHOR_NAME: "Frappe PR Bot"
GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io"
run: npx semantic-release

View File

@@ -1,100 +0,0 @@
name: Server
on:
pull_request:
paths-ignore:
- '**.js'
- '**.md'
workflow_dispatch:
push:
branches: [ develop ]
paths-ignore:
- '**.js'
- '**.md'
concurrency:
group: server-mariadb-v13-${{ github.event.number }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-18.04
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
container: [1, 2]
name: Python Unit Tests
services:
mysql:
image: mariadb:10.3
env:
MYSQL_ALLOW_EMPTY_PASSWORD: YES
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 12
check-latest: true
- name: Add to Hosts
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- name: Run Tests
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 2 --build-number ${{ matrix.container }}'
env:
TYPE: server
CI_BUILD_ID: ${{ github.run_id }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io

View File

@@ -1,22 +0,0 @@
name: Frappe Linter
on:
pull_request:
branches:
- develop
- version-12-hotfix
- version-11-hotfix
jobs:
check_translation:
name: Translation Syntax Check
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Setup python3
uses: actions/setup-python@v1
with:
python-version: 3.6
- name: Validating Translation Syntax
run: |
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
python $GITHUB_WORKSPACE/.github/helper/translation.py $files

View File

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

View File

@@ -1,45 +0,0 @@
exclude: 'node_modules|.git'
default_stages: [commit]
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: trailing-whitespace
files: "erpnext.*"
exclude: ".*json$|.*txt$|.*csv|.*md"
- id: check-yaml
- id: no-commit-to-branch
args: ['--branch', 'develop']
- id: check-merge-conflict
- id: check-ast
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear',
]
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"
- repo: https://github.com/adityahase/black
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
hooks:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/timothycrosley/isort
rev: 5.9.1
hooks:
- id: isort
exclude: ".*setup.py$"
ci:
autoupdate_schedule: weekly
skip: []
submodules: false

1
.pylintrc Normal file
View File

@@ -0,0 +1 @@
disable=access-member-before-definition

View File

@@ -1,24 +0,0 @@
{
"branches": ["version-13"],
"plugins": [
"@semantic-release/commit-analyzer", {
"preset": "angular",
"releaseRules": [
{"breaking": true, "release": false}
]
},
"@semantic-release/release-notes-generator",
[
"@semantic-release/exec", {
"prepareCmd": 'sed -ir -E "s/\"[0-9]+\.[0-9]+\.[0-9]+\"/\"${nextRelease.version}\"/" erpnext/__init__.py'
}
],
[
"@semantic-release/git", {
"assets": ["erpnext/__init__.py"],
"message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
]
}

80
.travis.yml Normal file
View File

@@ -0,0 +1,80 @@
dist: trusty
language: python
git:
depth: 1
cache:
- pip
addons:
hosts: test_site
mariadb: 10.3
jobs:
include:
- name: "Python 2.7 Server Side Test"
python: 2.7
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 3.6 Server Side Test"
python: 3.6
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 2.7 Patch Test"
python: 2.7
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
- name: "Python 3.6 Patch Test"
python: 3.6
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
install:
- cd ~
- nvm install 10
- git clone https://github.com/frappe/bench --depth 1
- pip install -e ./bench
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench
- mkdir ~/frappe-bench/sites/test_site
- cp -r $TRAVIS_BUILD_DIR/.travis/site_config.json ~/frappe-bench/sites/test_site/
- mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
- mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- mysql -u root -e "CREATE DATABASE test_frappe"
- mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
- mysql -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
- mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
- mysql -u root -e "FLUSH PRIVILEGES"
- wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
- tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
- sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
- sudo chmod o+x /usr/local/bin/wkhtmltopdf
- cd ~/frappe-bench
- sed -i 's/watch:/# watch:/g' Procfile
- sed -i 's/schedule:/# schedule:/g' Procfile
- sed -i 's/socketio:/# socketio:/g' Procfile
- sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
- bench get-app erpnext $TRAVIS_BUILD_DIR
- bench start &
- bench --site test_site reinstall --yes
after_script:
- pip install python-coveralls
- coveralls -b apps/erpnext -d ../../sites/.coverage

View File

@@ -1,6 +1,4 @@
{ {
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "test_frappe", "db_name": "test_frappe",
"db_password": "test_frappe", "db_password": "test_frappe",
"auto_email_id": "test@example.com", "auto_email_id": "test@example.com",
@@ -11,6 +9,5 @@
"root_login": "root", "root_login": "root",
"root_password": "travis", "root_password": "travis",
"host_name": "http://test_site:8000", "host_name": "http://test_site:8000",
"install_apps": ["erpnext"], "install_apps": ["erpnext"]
"throttle_user_limit": 100
} }

View File

@@ -1,33 +0,0 @@
# Each line is a file pattern followed by one or more owners.
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/erpnext_integrations/ @nextchamp-saqib
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/support/ @nextchamp-saqib @deepeshgarg007
pos* @nextchamp-saqib
erpnext/buying/ @rohitwaghchaure @s-aga-r
erpnext/maintenance/ @rohitwaghchaure @s-aga-r
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/healthcare/ @chillaranand
erpnext/hr/ @ruchamahabal
erpnext/non_profit/ @ruchamahabal
erpnext/payroll @ruchamahabal
erpnext/projects/ @ruchamahabal
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure
erpnext/public/ @nextchamp-saqib @marination
.github/ @ankush
requirements.txt @gavindsouza @ankush

View File

@@ -5,7 +5,7 @@
<p>ERP made simple</p> <p>ERP made simple</p>
</p> </p>
[![CI](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml) [![Build Status](https://travis-ci.com/frappe/erpnext.svg)](https://travis-ci.com/frappe/erpnext)
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext) [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop) [![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
@@ -13,36 +13,15 @@
</div> </div>
ERPNext as a monolith includes the following areas for managing businesses: Includes: Accounting, Inventory, Manufacturing, CRM, Sales, Purchase, Project Management, HRMS. Requires MariaDB.
1. [Accounting](https://erpnext.com/open-source-accounting) ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & JavaScript.
1. [Warehouse Management](https://erpnext.com/distribution/warehouse-management-system)
1. [CRM](https://erpnext.com/open-source-crm)
1. [Sales](https://erpnext.com/open-source-sales-purchase)
1. [Purchase](https://erpnext.com/open-source-sales-purchase)
1. [HRMS](https://erpnext.com/open-source-hrms)
1. [Project Management](https://erpnext.com/open-source-projects)
1. [Support](https://erpnext.com/open-source-help-desk-software)
1. [Asset Management](https://erpnext.com/open-source-asset-management-software)
1. [Quality Management](https://erpnext.com/docs/user/manual/en/quality-management)
1. [Manufacturing](https://erpnext.com/open-source-manufacturing-erp-software)
1. [Website Management](https://erpnext.com/open-source-website-builder-software)
1. [Customize ERPNext](https://erpnext.com/docs/user/manual/en/customize-erpnext)
1. [And More](https://erpnext.com/docs/user/manual/en/)
ERPNext requires MariaDB.
ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a full-stack web app framework built with Python & JavaScript.
- [User Guide](https://erpnext.com/docs/user) - [User Guide](https://erpnext.com/docs/user)
- [Discussion Forum](https://discuss.erpnext.com/) - [Discussion Forum](https://discuss.erpnext.com/)
--- ---
### Containerized Installation
Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details.
### Full Install ### Full Install
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details. The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
@@ -65,8 +44,6 @@ GNU/General Public License (see [license.txt](license.txt))
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors. The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
By contributing to ERPNext, you agree that your contributions will be licensed under its GNU General Public License (v3).
--- ---
## Contributing ## Contributing

View File

@@ -1,11 +1,3 @@
{ {
"baseUrl": "http://test_site:8000", "baseUrl": "http://test_site_ui:8000"
"projectId": "da59y9",
"adminPassword": "admin",
"defaultCommandTimeout": 20000,
"pageLoadTimeout": 15000,
"retries": {
"runMode": 2,
"openMode": 2
}
} }

View File

@@ -0,0 +1,31 @@
context('Form', () => {
before(() => {
cy.login('Administrator', 'qwe');
cy.visit('/desk');
});
it('create a new opportunity', () => {
cy.visit('/desk#Form/Opportunity/New Opportunity 1');
cy.get('.page-title').should('contain', 'Not Saved');
cy.fill_field('opportunity_from', 'Customer', 'Select');
cy.fill_field('party_name', 'Test Customer', 'Link').blur();
cy.get('.primary-action').click();
cy.get('.page-title').should('contain', 'Open');
cy.get('.form-inner-toolbar button:contains("Lost")').click({ force: true });
cy.get('.modal input[data-fieldname="lost_reason"]').as('input');
cy.get('@input').focus().type('Higher', { delay: 200 });
cy.get('.modal .awesomplete ul')
.should('be.visible')
.get('li:contains("Higher Price")')
.click({ force: true });
cy.get('@input').focus().type('No Followup', { delay: 200 });
cy.get('.modal .awesomplete ul')
.should('be.visible')
.get('li:contains("No Followup")')
.click();
cy.fill_field('detailed_reason', 'Test Detailed Reason', 'Text');
cy.get('.modal button:contains("Declare Lost")').click({ force: true });
cy.get('.page-title').should('contain', 'Lost');
});
});

View File

@@ -1,13 +0,0 @@
context('Customer', () => {
before(() => {
cy.login();
});
it('Check Customer Group', () => {
cy.visit(`app/customer/`);
cy.get('.primary-action').click();
cy.wait(500);
cy.get('.custom-actions > .btn').click();
cy.get_field('customer_group', 'Link').should('have.value', 'All Customer Groups');
});
});

View File

@@ -1,116 +0,0 @@
context('Organizational Chart', () => {
before(() => {
cy.login();
cy.visit('/app/website');
});
it('navigates to org chart', () => {
cy.visit('/app');
cy.visit('/app/organizational-chart');
cy.url().should('include', '/organizational-chart');
cy.window().its('frappe.csrf_token').then(csrf_token => {
return cy.request({
url: `/api/method/erpnext.tests.ui_test_helpers.create_employee_records`,
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Frappe-CSRF-Token': csrf_token
},
timeout: 60000
}).then(res => {
expect(res.status).eq(200);
cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
cy.get('@input')
.clear({ force: true })
.type('Test Org Chart{downarrow}{enter}', { force: true })
.blur({ force: true });
});
});
});
it('renders root nodes and loads children for the first expandable node', () => {
// check rendered root nodes and the node name, title, connections
cy.get('.hierarchy').find('.root-level ul.node-children').children()
.should('have.length', 2)
.first()
.as('first-child');
cy.get('@first-child').get('.node-name').contains('Test Employee 1');
cy.get('@first-child').get('.node-info').find('.node-title').contains('CEO');
cy.get('@first-child').get('.node-info').find('.node-connections').contains('· 2 Connections');
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// children of 1st root visible
cy.get(`div[data-parent="${employee_records.message[0]}"]`).as('child-node');
cy.get('@child-node')
.should('have.length', 1)
.should('be.visible');
cy.get('@child-node').get('.node-name').contains('Test Employee 3');
// connectors between first root node and immediate child
cy.get(`path[data-parent="${employee_records.message[0]}"]`)
.should('be.visible')
.invoke('attr', 'data-child')
.should('equal', employee_records.message[2]);
});
});
it('hides active nodes children and connectors on expanding sibling node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// click sibling
cy.get(`#${employee_records.message[1]}`)
.click()
.should('have.class', 'active');
// child nodes and connectors hidden
cy.get(`[data-parent="${employee_records.message[0]}"]`).should('not.be.visible');
cy.get(`path[data-parent="${employee_records.message[0]}"]`).should('not.be.visible');
});
});
it('collapses previous level nodes and refreshes connectors on expanding child node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// click child node
cy.get(`#${employee_records.message[3]}`)
.click()
.should('have.class', 'active');
// previous level nodes: parent should be on active-path; other nodes should be collapsed
cy.get(`#${employee_records.message[0]}`).should('have.class', 'collapsed');
cy.get(`#${employee_records.message[1]}`).should('have.class', 'active-path');
// previous level connectors refreshed
cy.get(`path[data-parent="${employee_records.message[1]}"]`)
.should('have.class', 'collapsed-connector');
// child node's children and connectors rendered
cy.get(`[data-parent="${employee_records.message[3]}"]`).should('be.visible');
cy.get(`path[data-parent="${employee_records.message[3]}"]`).should('be.visible');
});
});
it('expands previous level nodes', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[0]}`)
.click()
.should('have.class', 'active');
cy.get(`[data-parent="${employee_records.message[0]}"]`)
.should('be.visible');
cy.get('ul.hierarchy').children().should('have.length', 2);
cy.get(`#connectors`).children().should('have.length', 1);
});
});
it('edit node navigates to employee master', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[0]}`).find('.btn-edit-node')
.click();
cy.url().should('include', `/employee/${employee_records.message[0]}`);
});
});
});

View File

@@ -1,195 +0,0 @@
context('Organizational Chart Mobile', () => {
before(() => {
cy.login();
cy.visit('/app/website');
});
it('navigates to org chart', () => {
cy.viewport(375, 667);
cy.visit('/app');
cy.visit('/app/organizational-chart');
cy.url().should('include', '/organizational-chart');
cy.window().its('frappe.csrf_token').then(csrf_token => {
return cy.request({
url: `/api/method/erpnext.tests.ui_test_helpers.create_employee_records`,
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Frappe-CSRF-Token': csrf_token
},
timeout: 60000
}).then(res => {
expect(res.status).eq(200);
cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
cy.get('@input')
.clear({ force: true })
.type('Test Org Chart{downarrow}{enter}', { force: true })
.blur({ force: true });
});
});
});
it('renders root nodes', () => {
// check rendered root nodes and the node name, title, connections
cy.get('.hierarchy-mobile').find('.root-level').children()
.should('have.length', 2)
.first()
.as('first-child');
cy.get('@first-child').get('.node-name').contains('Test Employee 1');
cy.get('@first-child').get('.node-info').find('.node-title').contains('CEO');
cy.get('@first-child').get('.node-info').find('.node-connections').contains('· 2');
});
it('expands root node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[1]}`)
.click()
.should('have.class', 'active');
// other root node removed
cy.get(`#${employee_records.message[0]}`).should('not.exist');
// children of active root node
cy.get('.hierarchy-mobile').find('.level').first().find('ul.node-children').children()
.should('have.length', 2);
cy.get(`div[data-parent="${employee_records.message[1]}"]`).first().as('child-node');
cy.get('@child-node').should('be.visible');
cy.get('@child-node')
.get('.node-name')
.contains('Test Employee 4');
// connectors between root node and immediate children
cy.get(`path[data-parent="${employee_records.message[1]}"]`).as('connectors');
cy.get('@connectors')
.should('have.length', 2)
.should('be.visible');
cy.get('@connectors')
.first()
.invoke('attr', 'data-child')
.should('eq', employee_records.message[3]);
});
});
it('expands child node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[3]}`)
.click()
.should('have.class', 'active')
.as('expanded_node');
// 2 levels on screen; 1 on active path; 1 collapsed
cy.get('.hierarchy-mobile').children().should('have.length', 2);
cy.get(`#${employee_records.message[1]}`).should('have.class', 'active-path');
// children of expanded node visible
cy.get('@expanded_node')
.next()
.should('have.class', 'node-children')
.as('node-children');
cy.get('@node-children').children().should('have.length', 1);
cy.get('@node-children')
.first()
.get('.node-card')
.should('have.class', 'active-child')
.contains('Test Employee 7');
// orphan connectors removed
cy.get(`#connectors`).children().should('have.length', 2);
});
});
it('renders sibling group', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// sibling group visible for parent
cy.get(`#${employee_records.message[1]}`)
.next()
.as('sibling_group');
cy.get('@sibling_group')
.should('have.attr', 'data-parent', 'undefined')
.should('have.class', 'node-group')
.and('have.class', 'collapsed');
cy.get('@sibling_group').get('.avatar-group').children().as('siblings');
cy.get('@siblings').should('have.length', 1);
cy.get('@siblings')
.first()
.should('have.attr', 'title', 'Test Employee 1');
});
});
it('expands previous level nodes', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[6]}`)
.click()
.should('have.class', 'active');
// clicking on previous level node should remove all the nodes ahead
// and expand that node
cy.get(`#${employee_records.message[3]}`).click();
cy.get(`#${employee_records.message[3]}`)
.should('have.class', 'active')
.should('not.have.class', 'active-path');
cy.get(`#${employee_records.message[6]}`).should('have.class', 'active-child');
cy.get('.hierarchy-mobile').children().should('have.length', 2);
cy.get(`#connectors`).children().should('have.length', 2);
});
});
it('expands sibling group', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// sibling group visible for parent
cy.get(`#${employee_records.message[6]}`).click();
cy.get(`#${employee_records.message[3]}`)
.next()
.click();
// siblings of parent should be visible
cy.get('.hierarchy-mobile').prev().as('sibling_group');
cy.get('@sibling_group')
.should('exist')
.should('have.class', 'sibling-group')
.should('not.have.class', 'collapsed');
cy.get(`#${employee_records.message[1]}`)
.should('be.visible')
.should('have.class', 'active');
cy.get(`[data-parent="${employee_records.message[1]}"]`)
.should('be.visible')
.should('have.length', 2)
.should('have.class', 'active-child');
});
});
it('goes to the respective level after clicking on non-collapsed sibling group', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(() => {
// click on non-collapsed sibling group
cy.get('.hierarchy-mobile')
.prev()
.click();
// should take you to that level
cy.get('.hierarchy-mobile').find('li.level .node-card').should('have.length', 2);
});
});
it('edit node navigates to employee master', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[0]}`).find('.btn-edit-node')
.click();
cy.url().should('include', `/employee/${employee_records.message[0]}`);
});
});
});

View File

@@ -11,7 +11,7 @@
// This function is called when a project is opened or re-opened (e.g. due to // This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing) // the project's config changing)
module.exports = () => { // module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits // `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config // `config` is the resolved Cypress config
}; // }

View File

@@ -10,22 +10,16 @@
// //
// //
// -- This is a parent command -- // -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... }); // Cypress.Commands.add("login", (email, password) => { ... })
// //
// //
// -- This is a child command -- // -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }); // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
// //
// //
// -- This is a dual command -- // -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }); // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
// //
// //
// -- This is will overwrite an existing command -- // -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }); // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
const slug = (name) => name.toLowerCase().replace(" ", "-");
Cypress.Commands.add("go_to_doc", (doctype, name) => {
cy.visit(`/app/${slug(doctype)}/${encodeURIComponent(name)}`);
});

View File

@@ -13,14 +13,10 @@
// https://on.cypress.io/configuration // https://on.cypress.io/configuration
// *********************************************************** // ***********************************************************
// import frappe commands
import '../../../frappe/cypress/support/index';
// Import commands.js using ES2015 syntax: // Import commands.js using ES2015 syntax:
import './commands'; import './commands';
import '../../../frappe/cypress/support/commands' // eslint-disable-line
// Alternatively you can use CommonJS syntax: // Alternatively you can use CommonJS syntax:
// require('./commands') // require('./commands')
Cypress.Cookies.defaults({
preserve: 'sid'
});

View File

@@ -1,12 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"baseUrl": "../node_modules",
"types": [
"cypress"
]
},
"include": [
"**/*.*"
]
}

View File

@@ -1,9 +0,0 @@
{
"extends": ["stylelint-config-recommended"],
"plugins": ["stylelint-scss"],
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"no-descending-specificity": null
}
}

View File

@@ -1,60 +1,53 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import inspect import inspect
import frappe import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = "13.36.1" __version__ = '12.3.0'
def get_default_company(user=None): def get_default_company(user=None):
"""Get default company for user""" '''Get default company for user'''
from frappe.defaults import get_user_default_as_list from frappe.defaults import get_user_default_as_list
if not user: if not user:
user = frappe.session.user user = frappe.session.user
companies = get_user_default_as_list(user, "company") companies = get_user_default_as_list(user, 'company')
if companies: if companies:
default_company = companies[0] default_company = companies[0]
else: else:
default_company = frappe.db.get_single_value("Global Defaults", "default_company") default_company = frappe.db.get_single_value('Global Defaults', 'default_company')
return default_company return default_company
def get_default_currency(): def get_default_currency():
"""Returns the currency of the default company""" '''Returns the currency of the default company'''
company = get_default_company() company = get_default_company()
if company: if company:
return frappe.get_cached_value("Company", company, "default_currency") return frappe.get_cached_value('Company', company, 'default_currency')
def get_default_cost_center(company): def get_default_cost_center(company):
"""Returns the default cost center of the company""" '''Returns the default cost center of the company'''
if not company: if not company:
return None return None
if not frappe.flags.company_cost_center: if not frappe.flags.company_cost_center:
frappe.flags.company_cost_center = {} frappe.flags.company_cost_center = {}
if not company in frappe.flags.company_cost_center: if not company in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value( frappe.flags.company_cost_center[company] = frappe.get_cached_value('Company', company, 'cost_center')
"Company", company, "cost_center"
)
return frappe.flags.company_cost_center[company] return frappe.flags.company_cost_center[company]
def get_company_currency(company): def get_company_currency(company):
"""Returns the default company currency""" '''Returns the default company currency'''
if not frappe.flags.company_currency: if not frappe.flags.company_currency:
frappe.flags.company_currency = {} frappe.flags.company_currency = {}
if not company in frappe.flags.company_currency: if not company in frappe.flags.company_currency:
frappe.flags.company_currency[company] = frappe.db.get_value( frappe.flags.company_currency[company] = frappe.db.get_value('Company', company, 'default_currency', cache=True)
"Company", company, "default_currency", cache=True
)
return frappe.flags.company_currency[company] return frappe.flags.company_currency[company]
def set_perpetual_inventory(enable=1, company=None): def set_perpetual_inventory(enable=1, company=None):
if not company: if not company:
company = "_Test Company" if frappe.flags.in_test else get_default_company() company = "_Test Company" if frappe.flags.in_test else get_default_company()
@@ -63,10 +56,9 @@ def set_perpetual_inventory(enable=1, company=None):
company.enable_perpetual_inventory = enable company.enable_perpetual_inventory = enable
company.save() company.save()
def encode_company_abbr(name, company):
def encode_company_abbr(name, company=None, abbr=None): '''Returns name encoded with company abbreviation'''
"""Returns name encoded with company abbreviation""" company_abbr = frappe.get_cached_value('Company', company, "abbr")
company_abbr = abbr or frappe.get_cached_value("Company", company, "abbr")
parts = name.rsplit(" - ", 1) parts = name.rsplit(" - ", 1)
if parts[-1].lower() != company_abbr.lower(): if parts[-1].lower() != company_abbr.lower():
@@ -74,73 +66,65 @@ def encode_company_abbr(name, company=None, abbr=None):
return " - ".join(parts) return " - ".join(parts)
def is_perpetual_inventory_enabled(company): def is_perpetual_inventory_enabled(company):
if not company: if not company:
company = "_Test Company" if frappe.flags.in_test else get_default_company() company = "_Test Company" if frappe.flags.in_test else get_default_company()
if not hasattr(frappe.local, "enable_perpetual_inventory"): if not hasattr(frappe.local, 'enable_perpetual_inventory'):
frappe.local.enable_perpetual_inventory = {} frappe.local.enable_perpetual_inventory = {}
if not company in frappe.local.enable_perpetual_inventory: if not company in frappe.local.enable_perpetual_inventory:
frappe.local.enable_perpetual_inventory[company] = ( frappe.local.enable_perpetual_inventory[company] = frappe.get_cached_value('Company',
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0 company, "enable_perpetual_inventory") or 0
)
return frappe.local.enable_perpetual_inventory[company] return frappe.local.enable_perpetual_inventory[company]
def get_default_finance_book(company=None): def get_default_finance_book(company=None):
if not company: if not company:
company = get_default_company() company = get_default_company()
if not hasattr(frappe.local, "default_finance_book"): if not hasattr(frappe.local, 'default_finance_book'):
frappe.local.default_finance_book = {} frappe.local.default_finance_book = {}
if not company in frappe.local.default_finance_book: if not company in frappe.local.default_finance_book:
frappe.local.default_finance_book[company] = frappe.get_cached_value( frappe.local.default_finance_book[company] = frappe.get_cached_value('Company',
"Company", company, "default_finance_book" company, "default_finance_book")
)
return frappe.local.default_finance_book[company] return frappe.local.default_finance_book[company]
def get_party_account_type(party_type): def get_party_account_type(party_type):
if not hasattr(frappe.local, "party_account_types"): if not hasattr(frappe.local, 'party_account_types'):
frappe.local.party_account_types = {} frappe.local.party_account_types = {}
if not party_type in frappe.local.party_account_types: if not party_type in frappe.local.party_account_types:
frappe.local.party_account_types[party_type] = ( frappe.local.party_account_types[party_type] = frappe.db.get_value("Party Type",
frappe.db.get_value("Party Type", party_type, "account_type") or "" party_type, "account_type") or ''
)
return frappe.local.party_account_types[party_type] return frappe.local.party_account_types[party_type]
def get_region(company=None): def get_region(company=None):
"""Return the default country based on flag, company or global settings '''Return the default country based on flag, company or global settings
You can also set global company flag in `frappe.flags.company` You can also set global company flag in `frappe.flags.company`
""" '''
if company or frappe.flags.company: if company or frappe.flags.company:
return frappe.get_cached_value("Company", company or frappe.flags.company, "country") return frappe.get_cached_value('Company',
company or frappe.flags.company, 'country')
elif frappe.flags.country: elif frappe.flags.country:
return frappe.flags.country return frappe.flags.country
else: else:
return frappe.get_system_settings("country") return frappe.get_system_settings('country')
def allow_regional(fn): def allow_regional(fn):
"""Decorator to make a function regionally overridable '''Decorator to make a function regionally overridable
Example: Example:
@erpnext.allow_regional @erpnext.allow_regional
def myfunction(): def myfunction():
pass""" pass'''
def caller(*args, **kwargs): def caller(*args, **kwargs):
region = get_region() region = get_region()
fn_name = inspect.getmodule(fn).__name__ + "." + fn.__name__ fn_name = inspect.getmodule(fn).__name__ + '.' + fn.__name__
if region in regional_overrides and fn_name in regional_overrides[region]: if region in regional_overrides and fn_name in regional_overrides[region]:
return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs) return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs)
else: else:
@@ -148,17 +132,16 @@ def allow_regional(fn):
return caller return caller
def get_last_membership():
'''Returns last membership if exists'''
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
@frappe.whitelist() return last_membership and last_membership[0]
def get_last_membership(member):
"""Returns last membership if exists"""
last_membership = frappe.get_all(
"Membership",
"name,to_date,membership_type",
dict(member=member, paid=1),
order_by="to_date desc",
limit=1,
)
if last_membership: def is_member():
return last_membership[0] '''Returns true if the user is still a member'''
last_membership = get_last_membership()
if last_membership and getdate(last_membership.to_date) > getdate():
return True
return False

View File

@@ -1,58 +0,0 @@
{
"cards": [
{
"card": "Total Outgoing Bills"
},
{
"card": "Total Incoming Bills"
},
{
"card": "Total Incoming Payment"
},
{
"card": "Total Outgoing Payment"
}
],
"charts": [
{
"chart": "Profit and Loss",
"width": "Full"
},
{
"chart": "Incoming Bills (Purchase Invoice)",
"width": "Half"
},
{
"chart": "Outgoing Bills (Sales Invoice)",
"width": "Half"
},
{
"chart": "Accounts Receivable Ageing",
"width": "Half"
},
{
"chart": "Accounts Payable Ageing",
"width": "Half"
},
{
"chart": "Budget Variance",
"width": "Full"
},
{
"chart": "Bank Balance",
"width": "Full"
}
],
"creation": "2020-07-17 11:25:34.796608",
"dashboard_name": "Accounts",
"docstatus": 0,
"doctype": "Dashboard",
"idx": 0,
"is_default": 0,
"is_standard": 1,
"modified": "2020-07-22 13:07:34.540574",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts",
"owner": "Administrator"
}

View File

@@ -5,7 +5,6 @@
"_comments": null, "_comments": null,
"_liked_by": null, "_liked_by": null,
"_user_tags": null, "_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -18,24 +17,17 @@
"docstatus": 0, "docstatus": 0,
"dt": "Address", "dt": "Address",
"fetch_from": null, "fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "tax_category", "fieldname": "tax_category",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"hide_border": 0, "idx": 14,
"hide_days": 0,
"hide_seconds": 0,
"idx": 15,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"insert_after": "fax", "insert_after": "fax",
"label": "Tax Category", "label": "Tax Category",
"length": 0,
"mandatory_depends_on": null,
"modified": "2018-12-28 22:29:21.828090", "modified": "2018-12-28 22:29:21.828090",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Address-tax_category", "name": "Address-tax_category",
@@ -51,66 +43,6 @@
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"print_width": null, "print_width": null,
"read_only": 0, "read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2020-10-14 17:41:40.878179",
"default": "0",
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Address",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "is_your_company_address",
"fieldtype": "Check",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 20,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "linked_with",
"label": "Is Your Company Address",
"length": 0,
"mandatory_depends_on": null,
"modified": "2020-10-14 17:41:40.878179",
"modified_by": "Administrator",
"name": "Address-is_your_company_address",
"no_copy": 0,
"options": null,
"owner": "Administrator",
"parent": null,
"parentfield": null,
"parenttype": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,

View File

@@ -1,60 +0,0 @@
import frappe
from frappe import _
from frappe.contacts.doctype.address.address import (
Address,
get_address_display,
get_address_templates,
)
class ERPNextAddress(Address):
def validate(self):
self.validate_reference()
super(ERPNextAddress, self).validate()
def link_address(self):
"""Link address based on owner"""
if self.is_your_company_address:
return
return super(ERPNextAddress, self).link_address()
def validate_reference(self):
if self.is_your_company_address and not [
row for row in self.links if row.link_doctype == "Company"
]:
frappe.throw(
_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
title=_("Company Not Linked"),
)
def on_update(self):
"""
After Address is updated, update the related 'Primary Address' on Customer.
"""
address_display = get_address_display(self.as_dict())
filters = {"customer_primary_address": self.name}
customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
for customer_name in customers:
frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
@frappe.whitelist()
def get_shipping_address(company, address=None):
filters = [
["Dynamic Link", "link_doctype", "=", "Company"],
["Dynamic Link", "link_name", "=", company],
["Address", "is_your_company_address", "=", 1],
]
fields = ["*"]
if address and frappe.db.get_value("Dynamic Link", {"parent": address, "link_name": company}):
filters.append(["Address", "name", "=", address])
if not address:
filters.append(["Address", "is_shipping_address", "=", 1])
address = frappe.get_all("Address", filters=filters, fields=fields) or {}
if address:
address_as_dict = address[0]
name, address_template = get_address_templates(address_as_dict)
return address_as_dict.get("name"), frappe.render_template(address_template, address_as_dict)

View File

@@ -1,23 +0,0 @@
{
"chart_name": "Accounts Payable Ageing",
"chart_type": "Report",
"creation": "2020-07-17 11:25:34.564015",
"docstatus": 0,
"doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"report_date\":\"frappe.datetime.now_date()\"}",
"filters_json": "{\"ageing_based_on\":\"Due Date\",\"range1\":30,\"range2\":60,\"range3\":90,\"range4\":120,\"group_by_party\":0,\"based_on_payment_terms\":0}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-22 12:29:33.584419",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Payable Ageing",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Accounts Payable",
"timeseries": 0,
"type": "Donut",
"use_report_chart": 1,
"y_axis": []
}

View File

@@ -1,23 +0,0 @@
{
"chart_name": "Accounts Receivable Ageing",
"chart_type": "Report",
"creation": "2020-07-17 11:25:34.535388",
"docstatus": 0,
"doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"report_date\":\"frappe.datetime.now_date()\"}",
"filters_json": "{\"ageing_based_on\":\"Due Date\",\"range1\":30,\"range2\":60,\"range3\":90,\"range4\":120,\"group_by_party\":0,\"based_on_payment_terms\":0,\"show_future_payments\":0,\"show_delivery_notes\":0,\"show_sales_person\":0}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-22 12:28:42.743551",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Receivable Ageing",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Accounts Receivable",
"timeseries": 0,
"type": "Donut",
"use_report_chart": 1,
"y_axis": []
}

View File

@@ -1,26 +0,0 @@
{
"chart_name": "Bank Balance",
"chart_type": "Custom",
"creation": "2020-07-17 11:25:34.620221",
"docstatus": 0,
"doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"account\":\"locals[\\\":Company\\\"][frappe.defaults.get_user_default(\\\"Company\\\")][\\\"default_bank_account\\\"]\"}",
"filters_json": "{}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"last_synced_on": "2020-07-22 12:19:59.879476",
"modified": "2020-07-22 12:21:48.780513",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Balance",
"number_of_groups": 0,
"owner": "Administrator",
"source": "Account Balance Timeline",
"time_interval": "Quarterly",
"timeseries": 0,
"timespan": "Last Year",
"type": "Line",
"use_report_chart": 0,
"y_axis": []
}

View File

@@ -1,23 +0,0 @@
{
"chart_name": "Budget Variance",
"chart_type": "Report",
"creation": "2020-07-17 11:25:34.593061",
"docstatus": 0,
"doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
"filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-22 12:24:49.144210",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Budget Variance",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Budget Variance Report",
"timeseries": 0,
"type": "Bar",
"use_report_chart": 1,
"y_axis": []
}

View File

@@ -1,29 +0,0 @@
{
"based_on": "posting_date",
"chart_name": "Incoming Bills (Purchase Invoice)",
"chart_type": "Sum",
"color": "#a83333",
"creation": "2020-07-17 11:25:34.479703",
"docstatus": 0,
"doctype": "Dashboard Chart",
"document_type": "Purchase Invoice",
"dynamic_filters_json": "",
"filters_json": "[[\"Purchase Invoice\",\"docstatus\",\"=\",1]]",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"last_synced_on": "2020-07-21 17:37:30.727306",
"modified": "2020-07-21 17:51:07.374917",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Incoming Bills (Purchase Invoice)",
"number_of_groups": 0,
"owner": "Administrator",
"time_interval": "Monthly",
"timeseries": 1,
"timespan": "Last Year",
"type": "Bar",
"use_report_chart": 0,
"value_based_on": "base_net_total",
"y_axis": []
}

View File

@@ -1,28 +0,0 @@
{
"based_on": "posting_date",
"chart_name": "Outgoing Bills (Sales Invoice)",
"chart_type": "Sum",
"color": "#7b933d",
"creation": "2020-07-17 11:25:34.507547",
"docstatus": 0,
"doctype": "Dashboard Chart",
"document_type": "Sales Invoice",
"filters_json": "[[\"Sales Invoice\",\"docstatus\",\"=\",1]]",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"last_synced_on": "2020-07-21 17:37:31.574666",
"modified": "2020-07-21 17:52:03.970530",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Outgoing Bills (Sales Invoice)",
"number_of_groups": 0,
"owner": "Administrator",
"time_interval": "Monthly",
"timeseries": 1,
"timespan": "Last Year",
"type": "Bar",
"use_report_chart": 0,
"value_based_on": "base_net_total",
"y_axis": []
}

View File

@@ -1,23 +0,0 @@
{
"chart_name": "Profit and Loss",
"chart_type": "Report",
"creation": "2020-07-17 11:25:34.448572",
"docstatus": 0,
"doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
"filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2020-07-22 12:33:48.888943",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Profit and Loss",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Profit and Loss Statement",
"timeseries": 0,
"type": "Bar",
"use_report_chart": 1,
"y_axis": []
}

View File

@@ -1,62 +1,42 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe import frappe, json
from frappe import _ from frappe import _
from frappe.utils import add_to_date, formatdate, get_link_to_form, getdate, nowdate from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate
from frappe.utils.dashboard import cache_source from erpnext.accounts.report.general_ledger.general_ledger import execute
from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan
from frappe.utils.nestedset import get_descendants_of from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist() @frappe.whitelist()
@cache_source @cache_source
def get( def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_date = None):
chart_name=None,
chart=None,
no_cache=None,
filters=None,
from_date=None,
to_date=None,
timespan=None,
time_interval=None,
heatmap_year=None,
):
if chart_name: if chart_name:
chart = frappe.get_doc("Dashboard Chart", chart_name) chart = frappe.get_doc('Dashboard Chart', chart_name)
else: else:
chart = frappe._dict(frappe.parse_json(chart)) chart = frappe._dict(frappe.parse_json(chart))
timespan = chart.timespan timespan = chart.timespan
if chart.timespan == "Select Date Range": if chart.timespan == 'Select Date Range':
from_date = chart.from_date from_date = chart.from_date
to_date = chart.to_date to_date = chart.to_date
timegrain = chart.time_interval timegrain = chart.time_interval
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json) filters = frappe.parse_json(chart.filters_json)
account = filters.get("account") account = filters.get("account")
company = filters.get("company") company = filters.get("company")
if not account and chart_name: if not account and chart:
frappe.throw( frappe.throw(_("Account is not set for the dashboard chart {0}").format(chart))
_("Account is not set for the dashboard chart {0}").format(
get_link_to_form("Dashboard Chart", chart_name)
)
)
if not frappe.db.exists("Account", account) and chart_name:
frappe.throw(
_("Account {0} does not exists in the dashboard chart {1}").format(
account, get_link_to_form("Dashboard Chart", chart_name)
)
)
if not to_date: if not to_date:
to_date = nowdate() to_date = nowdate()
if not from_date: if not from_date:
if timegrain in ("Monthly", "Quarterly"): if timegrain in ('Monthly', 'Quarterly'):
from_date = get_from_date_from_timespan(to_date, timespan) from_date = get_from_date_from_timespan(to_date, timespan)
# fetch dates to plot # fetch dates to plot
@@ -69,14 +49,16 @@ def get(
result = build_result(account, dates, gl_entries) result = build_result(account, dates, gl_entries)
return { return {
"labels": [formatdate(r[0].strftime("%Y-%m-%d")) for r in result], "labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result],
"datasets": [{"name": account, "values": [r[1] for r in result]}], "datasets": [{
"name": account,
"values": [r[1] for r in result]
}]
} }
def build_result(account, dates, gl_entries): def build_result(account, dates, gl_entries):
result = [[getdate(date), 0.0] for date in dates] result = [[getdate(date), 0.0] for date in dates]
root_type = frappe.db.get_value("Account", account, "root_type") root_type = frappe.db.get_value('Account', account, 'root_type')
# start with the first date # start with the first date
date_index = 0 date_index = 0
@@ -91,34 +73,30 @@ def build_result(account, dates, gl_entries):
result[date_index][1] += entry.debit - entry.credit result[date_index][1] += entry.debit - entry.credit
# if account type is credit, switch balances # if account type is credit, switch balances
if root_type not in ("Asset", "Expense"): if root_type not in ('Asset', 'Expense'):
for r in result: for r in result:
r[1] = -1 * r[1] r[1] = -1 * r[1]
# for balance sheet accounts, the totals are cumulative # for balance sheet accounts, the totals are cumulative
if root_type in ("Asset", "Liability", "Equity"): if root_type in ('Asset', 'Liability', 'Equity'):
for i, r in enumerate(result): for i, r in enumerate(result):
if i > 0: if i > 0:
r[1] = r[1] + result[i - 1][1] r[1] = r[1] + result[i-1][1]
return result return result
def get_gl_entries(account, to_date): def get_gl_entries(account, to_date):
child_accounts = get_descendants_of("Account", account, ignore_permissions=True) child_accounts = get_descendants_of('Account', account, ignore_permissions=True)
child_accounts.append(account) child_accounts.append(account)
return frappe.db.get_all( return frappe.db.get_all('GL Entry',
"GL Entry", fields = ['posting_date', 'debit', 'credit'],
fields=["posting_date", "debit", "credit"], filters = [
filters=[ dict(posting_date = ('<', to_date)),
dict(posting_date=("<", to_date)), dict(account = ('in', child_accounts)),
dict(account=("in", child_accounts)), dict(voucher_type = ('!=', 'Period Closing Voucher'))
dict(voucher_type=("!=", "Period Closing Voucher")),
], ],
order_by="posting_date asc", order_by = 'posting_date asc')
)
def get_dates_from_timegrain(from_date, to_date, timegrain): def get_dates_from_timegrain(from_date, to_date, timegrain):
days = months = years = 0 days = months = years = 0
@@ -133,8 +111,6 @@ def get_dates_from_timegrain(from_date, to_date, timegrain):
dates = [get_period_ending(from_date, timegrain)] dates = [get_period_ending(from_date, timegrain)]
while getdate(dates[-1]) < getdate(to_date): while getdate(dates[-1]) < getdate(to_date):
date = get_period_ending( date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain)
add_to_date(dates[-1], years=years, months=months, days=days), timegrain
)
dates.append(date) dates.append(date)
return dates return dates

View File

@@ -1,44 +1,26 @@
from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.email import sendmail_to_system_managers from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day
from frappe.utils import (
add_days,
add_months,
cint,
date_diff,
flt,
get_first_day,
get_last_day,
get_link_to_form,
getdate,
rounded,
today,
)
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from frappe.email import sendmail_to_system_managers
def validate_service_stop_date(doc): def validate_service_stop_date(doc):
"""Validates service_stop_date for Purchase Invoice and Sales Invoice""" ''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
enable_check = ( enable_check = "enable_deferred_revenue" \
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense" if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
)
old_stop_dates = {} old_stop_dates = {}
old_doc = frappe.db.get_all( old_doc = frappe.db.get_all("{0} Item".format(doc.doctype),
"{0} Item".format(doc.doctype), {"parent": doc.name}, ["name", "service_stop_date"] {"parent": doc.name}, ["name", "service_stop_date"])
)
for d in old_doc: for d in old_doc:
old_stop_dates[d.name] = d.service_stop_date or "" old_stop_dates[d.name] = d.service_stop_date or ""
for item in doc.items: for item in doc.items:
if not item.get(enable_check): if not item.get(enable_check): continue
continue
if item.service_stop_date: if item.service_stop_date:
if date_diff(item.service_stop_date, item.service_start_date) < 0: if date_diff(item.service_stop_date, item.service_start_date) < 0:
@@ -47,94 +29,45 @@ def validate_service_stop_date(doc):
if date_diff(item.service_stop_date, item.service_end_date) > 0: if date_diff(item.service_stop_date, item.service_end_date) > 0:
frappe.throw(_("Service Stop Date cannot be after Service End Date")) frappe.throw(_("Service Stop Date cannot be after Service End Date"))
if ( if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
old_stop_dates frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx)))
and old_stop_dates.get(item.name)
and item.service_stop_date != old_stop_dates.get(item.name)
):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
def convert_deferred_expense_to_expense(start_date=None, end_date=None):
def build_conditions(process_type, account, company):
conditions = ""
deferred_account = (
"item.deferred_revenue_account" if process_type == "Income" else "item.deferred_expense_account"
)
if account:
conditions += "AND %s='%s'" % (deferred_account, account)
elif company:
conditions += f"AND p.company = {frappe.db.escape(company)}"
return conditions
def convert_deferred_expense_to_expense(
deferred_process, start_date=None, end_date=None, conditions=""
):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date: if not start_date:
start_date = add_months(today(), -1) start_date = add_months(today(), -1)
if not end_date: if not end_date:
end_date = add_days(today(), -1) end_date = add_days(today(), -1)
# check for the purchase invoice for which GL entries has to be done # check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list( invoices = frappe.db.sql_list('''
""" select distinct parent from `tabPurchase Invoice Item`
select distinct item.parent where service_start_date<=%s and service_end_date>=%s
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0
where item.service_start_date<=%s and item.service_end_date>=%s ''', (end_date, start_date))
and item.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
""".format(
conditions
),
(end_date, start_date),
) # nosec
# For each invoice, book deferred expense # For each invoice, book deferred expense
for invoice in invoices: for invoice in invoices:
doc = frappe.get_doc("Purchase Invoice", invoice) doc = frappe.get_doc("Purchase Invoice", invoice)
book_deferred_income_or_expense(doc, deferred_process, end_date) book_deferred_income_or_expense(doc, end_date)
if frappe.flags.deferred_accounting_error: def convert_deferred_revenue_to_income(start_date=None, end_date=None):
send_mail(deferred_process)
def convert_deferred_revenue_to_income(
deferred_process, start_date=None, end_date=None, conditions=""
):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date: if not start_date:
start_date = add_months(today(), -1) start_date = add_months(today(), -1)
if not end_date: if not end_date:
end_date = add_days(today(), -1) end_date = add_days(today(), -1)
# check for the sales invoice for which GL entries has to be done # check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list( invoices = frappe.db.sql_list('''
""" select distinct parent from `tabSales Invoice Item`
select distinct item.parent where service_start_date<=%s and service_end_date>=%s
from `tabSales Invoice Item` item, `tabSales Invoice` p and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0
where item.service_start_date<=%s and item.service_end_date>=%s ''', (end_date, start_date))
and item.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
""".format(
conditions
),
(end_date, start_date),
) # nosec
for invoice in invoices: for invoice in invoices:
doc = frappe.get_doc("Sales Invoice", invoice) doc = frappe.get_doc("Sales Invoice", invoice)
book_deferred_income_or_expense(doc, deferred_process, end_date) book_deferred_income_or_expense(doc, end_date)
if frappe.flags.deferred_accounting_error:
send_mail(deferred_process)
def get_booking_dates(doc, item, posting_date=None): def get_booking_dates(doc, item, posting_date=None):
if not posting_date: if not posting_date:
@@ -142,37 +75,13 @@ def get_booking_dates(doc, item, posting_date=None):
last_gl_entry = False last_gl_entry = False
deferred_account = ( deferred_account = "deferred_revenue_account" if doc.doctype=="Sales Invoice" else "deferred_expense_account"
"deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
)
prev_gl_entry = frappe.db.sql( prev_gl_entry = frappe.db.sql('''
"""
select name, posting_date from `tabGL Entry` where company=%s and account=%s and select name, posting_date from `tabGL Entry` where company=%s and account=%s and
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
and is_cancelled = 0
order by posting_date desc limit 1 order by posting_date desc limit 1
""", ''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
prev_gl_via_je = frappe.db.sql(
"""
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
WHERE p.name = c.parent and p.company=%s and c.account=%s
and c.reference_type=%s and c.reference_name=%s
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
""",
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
if prev_gl_via_je:
if (not prev_gl_entry) or (
prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
):
prev_gl_entry = prev_gl_via_je
if prev_gl_entry: if prev_gl_entry:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1)) start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
@@ -195,95 +104,7 @@ def get_booking_dates(doc, item, posting_date=None):
else: else:
return None, None, None return None, None, None
def calculate_monthly_amount(
doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency
):
amount, base_amount = 0, 0
if not last_gl_entry:
total_months = (
(item.service_end_date.year - item.service_start_date.year) * 12
+ (item.service_end_date.month - item.service_start_date.month)
+ 1
)
prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) / flt(
date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date))
)
actual_months = rounded(total_months * prorate_factor, 1)
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount"))
if base_amount + already_booked_amount > item.base_net_amount:
base_amount = item.base_net_amount - already_booked_amount
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(item.net_amount / actual_months, item.precision("net_amount"))
if amount + already_booked_amount_in_account_currency > item.net_amount:
amount = item.net_amount - already_booked_amount_in_account_currency
if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
partial_month = flt(date_diff(end_date, start_date)) / flt(
date_diff(get_last_day(end_date), get_first_day(start_date))
)
base_amount = rounded(partial_month, 1) * base_amount
amount = rounded(partial_month, 1) * amount
else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(
item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
)
return amount, base_amount
def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency): def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
amount, base_amount = 0, 0
if not last_gl_entry:
base_amount = flt(
item.base_net_amount * total_booking_days / flt(total_days), item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(
item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
)
else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item
)
base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency:
amount = base_amount
else:
amount = flt(
item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
)
return amount, base_amount
def get_already_booked_amount(doc, item):
if doc.doctype == "Sales Invoice": if doc.doctype == "Sales Invoice":
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency" total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
deferred_account = "deferred_revenue_account" deferred_account = "deferred_revenue_account"
@@ -291,63 +112,39 @@ def get_already_booked_amount(doc, item):
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency" total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
deferred_account = "deferred_expense_account" deferred_account = "deferred_expense_account"
gl_entries_details = frappe.db.sql( amount, base_amount = 0, 0
""" if not last_gl_entry:
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount"))
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s if account_currency==doc.company_currency:
and is_cancelled = 0 amount = base_amount
group by voucher_detail_no else:
""".format( amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount"))
total_credit_debit, total_credit_debit_currency
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
journal_entry_details = frappe.db.sql(
"""
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
and p.docstatus < 2 group by reference_detail_no
""".format(
total_credit_debit, total_credit_debit_currency
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True,
)
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
already_booked_amount += journal_entry_details[0].total_credit if journal_entry_details else 0
if doc.currency == doc.company_currency:
already_booked_amount_in_account_currency = already_booked_amount
else: else:
already_booked_amount_in_account_currency = ( gl_entries_details = frappe.db.sql('''
gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0 select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
) from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
already_booked_amount_in_account_currency += ( group by voucher_detail_no
journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0 '''.format(total_credit_debit, total_credit_debit_currency),
) (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
if account_currency==doc.company_currency:
amount = base_amount
else:
already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
return already_booked_amount, already_booked_amount_in_account_currency return amount, base_amount
def book_deferred_income_or_expense(doc, posting_date=None):
enable_check = "enable_deferred_revenue" \
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): def _book_deferred_revenue_or_expense(item):
enable_check = (
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "acc_frozen_upto")
def _book_deferred_revenue_or_expense(
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
):
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date) start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
if not (start_date and end_date): if not (start_date and end_date): return
return
account_currency = get_account_currency(item.expense_account or item.income_account) account_currency = get_account_currency(item.expense_account)
if doc.doctype == "Sales Invoice": if doc.doctype == "Sales Invoice":
against, project = doc.customer, doc.project against, project = doc.customer, doc.project
credit_account, debit_account = item.income_account, item.deferred_revenue_account credit_account, debit_account = item.income_account, item.deferred_revenue_account
@@ -358,305 +155,61 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
total_days = date_diff(item.service_end_date, item.service_start_date) + 1 total_days = date_diff(item.service_end_date, item.service_start_date) + 1
total_booking_days = date_diff(end_date, start_date) + 1 total_booking_days = date_diff(end_date, start_date) + 1
if book_deferred_entries_based_on == "Months": amount, base_amount = calculate_amount(doc, item, last_gl_entry,
amount, base_amount = calculate_monthly_amount( total_days, total_booking_days, account_currency)
doc,
item,
last_gl_entry,
start_date,
end_date,
total_days,
total_booking_days,
account_currency,
)
else:
amount, base_amount = calculate_amount(
doc, item, last_gl_entry, total_days, total_booking_days, account_currency
)
if not amount: make_gl_entries(doc, credit_account, debit_account, against,
return amount, base_amount, end_date, project, account_currency, item.cost_center, item.name)
# check if books nor frozen till endate:
if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto):
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
if via_journal_entry:
book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
end_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
submit_journal_entry,
)
else:
make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
end_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
)
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
if frappe.flags.deferred_accounting_error:
return
if getdate(end_date) < getdate(posting_date) and not last_gl_entry: if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense( _book_deferred_revenue_or_expense(item)
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
)
via_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry")
)
submit_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")
)
book_deferred_entries_based_on = frappe.db.get_singles_value(
"Accounts Settings", "book_deferred_entries_based_on"
)
for item in doc.get("items"): for item in doc.get('items'):
if item.get(enable_check): if item.get(enable_check):
_book_deferred_revenue_or_expense( _book_deferred_revenue_or_expense(item)
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
)
def make_gl_entries(doc, credit_account, debit_account, against,
def process_deferred_accounting(posting_date=None): amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no):
"""Converts deferred income/expense into income/expense
Executed via background jobs on every month end"""
if not posting_date:
posting_date = today()
if not cint(
frappe.db.get_singles_value(
"Accounts Settings", "automatically_process_deferred_accounting_entry"
)
):
return
start_date = add_months(today(), -1)
end_date = add_days(today(), -1)
companies = frappe.get_all("Company")
for company in companies:
for record_type in ("Income", "Expense"):
doc = frappe.get_doc(
dict(
doctype="Process Deferred Accounting",
company=company.name,
posting_date=posting_date,
start_date=start_date,
end_date=end_date,
type=record_type,
)
)
doc.insert()
doc.submit()
def make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
posting_date,
project,
account_currency,
cost_center,
item,
deferred_process=None,
):
# GL Entry for crediting the amount in the deferred expense # GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
if amount == 0: if amount == 0: return
return
gl_entries = [] gl_entries = []
gl_entries.append( gl_entries.append(
doc.get_gl_dict( doc.get_gl_dict({
{ "account": credit_account,
"account": credit_account, "against": against,
"against": against, "credit": base_amount,
"credit": base_amount, "credit_in_account_currency": amount,
"credit_in_account_currency": amount, "cost_center": cost_center,
"cost_center": cost_center, "voucher_detail_no": voucher_detail_no,
"voucher_detail_no": item.name, 'posting_date': posting_date,
"posting_date": posting_date, 'project': project
"project": project, }, account_currency)
"against_voucher_type": "Process Deferred Accounting",
"against_voucher": deferred_process,
},
account_currency,
item=item,
)
) )
# GL Entry to debit the amount from the expense # GL Entry to debit the amount from the expense
gl_entries.append( gl_entries.append(
doc.get_gl_dict( doc.get_gl_dict({
{ "account": debit_account,
"account": debit_account, "against": against,
"against": against, "debit": base_amount,
"debit": base_amount, "debit_in_account_currency": amount,
"debit_in_account_currency": amount, "cost_center": cost_center,
"cost_center": cost_center, "voucher_detail_no": voucher_detail_no,
"voucher_detail_no": item.name, 'posting_date': posting_date,
"posting_date": posting_date, 'project': project
"project": project, }, account_currency)
"against_voucher_type": "Process Deferred Accounting",
"against_voucher": deferred_process,
},
account_currency,
item=item,
)
) )
if gl_entries: if gl_entries:
try: try:
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True) make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
frappe.db.commit() frappe.db.commit()
except Exception as e: except:
if frappe.flags.in_test: frappe.db.rollback()
traceback = frappe.get_traceback() title = _("Error while processing deferred accounting for {0}").format(doc.name)
frappe.log_error( traceback = frappe.get_traceback()
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name), frappe.log_error(message=traceback , title=title)
message=traceback, sendmail_to_system_managers(title, traceback)
)
raise e
else:
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
message=traceback,
)
frappe.flags.deferred_accounting_error = True
def send_mail(deferred_process):
title = _("Error while processing deferred accounting for {0}").format(deferred_process)
link = get_link_to_form("Process Deferred Accounting", deferred_process)
content = _("Deferred accounting failed for some invoices:") + "\n"
content += _(
"Please check Process Deferred Accounting {0} and submit manually after resolving errors."
).format(link)
sendmail_to_system_managers(title, content)
def book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
posting_date,
project,
account_currency,
cost_center,
item,
deferred_process=None,
submit="No",
):
if amount == 0:
return
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.posting_date = posting_date
journal_entry.company = doc.company
journal_entry.voucher_type = (
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
)
journal_entry.process_deferred_accounting = deferred_process
debit_entry = {
"account": credit_account,
"credit": base_amount,
"credit_in_account_currency": amount,
"account_currency": account_currency,
"reference_name": doc.name,
"reference_type": doc.doctype,
"reference_detail_no": item.name,
"cost_center": cost_center,
"project": project,
}
credit_entry = {
"account": debit_account,
"debit": base_amount,
"debit_in_account_currency": amount,
"account_currency": account_currency,
"reference_name": doc.name,
"reference_type": doc.doctype,
"reference_detail_no": item.name,
"cost_center": cost_center,
"project": project,
}
for dimension in get_accounting_dimensions():
debit_entry.update({dimension: item.get(dimension)})
credit_entry.update({dimension: item.get(dimension)})
journal_entry.append("accounts", debit_entry)
journal_entry.append("accounts", credit_entry)
try:
journal_entry.save()
if submit:
journal_entry.submit()
frappe.db.commit()
except Exception:
frappe.db.rollback()
traceback = frappe.get_traceback()
frappe.log_error(
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
message=traceback,
)
frappe.flags.deferred_accounting_error = True
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
if doctype == "Sales Invoice":
credit_account, debit_account = frappe.db.get_value(
"Sales Invoice Item",
{"name": voucher_detail_no},
["income_account", "deferred_revenue_account"],
)
else:
credit_account, debit_account = frappe.db.get_value(
"Purchase Invoice Item",
{"name": voucher_detail_no},
["deferred_expense_account", "expense_account"],
)
if dr_or_cr == "Debit":
return debit_account
else:
return credit_account

View File

@@ -0,0 +1 @@
from __future__ import unicode_literals

View File

@@ -0,0 +1 @@
from __future__ import unicode_literals

View File

@@ -43,12 +43,12 @@ frappe.ui.form.on('Account', {
frm.trigger('add_toolbar_buttons'); frm.trigger('add_toolbar_buttons');
} }
if (frm.has_perm('write')) { if (frm.has_perm('write')) {
frm.add_custom_button(__('Merge Account'), function () {
frm.trigger("merge_account");
}, __('Actions'));
frm.add_custom_button(__('Update Account Name / Number'), function () { frm.add_custom_button(__('Update Account Name / Number'), function () {
frm.trigger("update_account_number"); frm.trigger("update_account_number");
}, __('Actions')); });
frm.add_custom_button(__('Merge Account'), function () {
frm.trigger("merge_account");
});
} }
} }
}, },
@@ -59,12 +59,11 @@ frappe.ui.form.on('Account', {
} }
}, },
add_toolbar_buttons: function(frm) { add_toolbar_buttons: function(frm) {
frm.add_custom_button(__('Chart of Accounts'), () => { frm.add_custom_button(__('Chart of Accounts'),
frappe.set_route("Tree", "Account"); function () { frappe.set_route("Tree", "Account"); });
}, __('View'));
if (frm.doc.is_group == 1) { if (frm.doc.is_group == 1) {
frm.add_custom_button(__('Convert to Non-Group'), function () { frm.add_custom_button(__('Group to Non-Group'), function () {
return frappe.call({ return frappe.call({
doc: frm.doc, doc: frm.doc,
method: 'convert_group_to_ledger', method: 'convert_group_to_ledger',
@@ -72,11 +71,10 @@ frappe.ui.form.on('Account', {
frm.refresh(); frm.refresh();
} }
}); });
}, __('Actions')); });
} else if (cint(frm.doc.is_group) == 0 } else if (cint(frm.doc.is_group) == 0
&& frappe.boot.user.can_read.indexOf("GL Entry") !== -1) { && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
frm.add_custom_button(__('General Ledger'), function () { cur_frm.add_custom_button(__('Ledger'), function () {
frappe.route_options = { frappe.route_options = {
"account": frm.doc.name, "account": frm.doc.name,
"from_date": frappe.sys_defaults.year_start_date, "from_date": frappe.sys_defaults.year_start_date,
@@ -84,9 +82,9 @@ frappe.ui.form.on('Account', {
"company": frm.doc.company "company": frm.doc.company
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __('View')); });
frm.add_custom_button(__('Convert to Group'), function () { frm.add_custom_button(__('Non-Group to Group'), function () {
return frappe.call({ return frappe.call({
doc: frm.doc, doc: frm.doc,
method: 'convert_ledger_to_group', method: 'convert_ledger_to_group',
@@ -94,7 +92,7 @@ frappe.ui.form.on('Account', {
frm.refresh(); frm.refresh();
} }
}); });
}, __('Actions')); });
} }
}, },

View File

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

View File

@@ -1,47 +1,35 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe import frappe
from frappe import _, throw
from frappe.utils import cint, cstr from frappe.utils import cint, cstr
from frappe import throw, _
from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of
import erpnext class RootNotEditable(frappe.ValidationError): pass
class BalanceMismatchError(frappe.ValidationError): pass
class RootNotEditable(frappe.ValidationError):
pass
class BalanceMismatchError(frappe.ValidationError):
pass
class Account(NestedSet): class Account(NestedSet):
nsm_parent_field = "parent_account" nsm_parent_field = 'parent_account'
def on_update(self): def on_update(self):
if frappe.local.flags.ignore_update_nsm: if frappe.local.flags.ignore_on_update:
return return
else: else:
super(Account, self).on_update() super(Account, self).on_update()
def onload(self): def onload(self):
frozen_accounts_modifier = frappe.db.get_value( frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", "Accounts Settings",
"Accounts Settings", "Accounts Settings", "frozen_accounts_modifier" "frozen_accounts_modifier")
)
if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles(): if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
self.set_onload("can_freeze_account", True) self.set_onload("can_freeze_account", True)
def autoname(self): def autoname(self):
from erpnext.accounts.utils import get_autoname_with_number from erpnext.accounts.utils import get_autoname_with_number
self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company) self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company)
def validate(self): def validate(self):
from erpnext.accounts.utils import validate_field_number from erpnext.accounts.utils import validate_field_number
if frappe.local.flags.allow_unverified_charts: if frappe.local.flags.allow_unverified_charts:
return return
self.validate_parent() self.validate_parent()
@@ -58,33 +46,22 @@ class Account(NestedSet):
def validate_parent(self): def validate_parent(self):
"""Fetch Parent Details and validate parent account""" """Fetch Parent Details and validate parent account"""
if self.parent_account: if self.parent_account:
par = frappe.db.get_value( par = frappe.db.get_value("Account", self.parent_account,
"Account", self.parent_account, ["name", "is_group", "company"], as_dict=1 ["name", "is_group", "company"], as_dict=1)
)
if not par: if not par:
throw( throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account))
_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account)
)
elif par.name == self.name: elif par.name == self.name:
throw(_("Account {0}: You can not assign itself as parent account").format(self.name)) throw(_("Account {0}: You can not assign itself as parent account").format(self.name))
elif not par.is_group: elif not par.is_group:
throw( throw(_("Account {0}: Parent account {1} can not be a ledger").format(self.name, self.parent_account))
_("Account {0}: Parent account {1} can not be a ledger").format(
self.name, self.parent_account
)
)
elif par.company != self.company: elif par.company != self.company:
throw( throw(_("Account {0}: Parent account {1} does not belong to company: {2}")
_("Account {0}: Parent account {1} does not belong to company: {2}").format( .format(self.name, self.parent_account, self.company))
self.name, self.parent_account, self.company
)
)
def set_root_and_report_type(self): def set_root_and_report_type(self):
if self.parent_account: if self.parent_account:
par = frappe.db.get_value( par = frappe.db.get_value("Account", self.parent_account,
"Account", self.parent_account, ["report_type", "root_type"], as_dict=1 ["report_type", "root_type"], as_dict=1)
)
if par.report_type: if par.report_type:
self.report_type = par.report_type self.report_type = par.report_type
@@ -95,20 +72,15 @@ class Account(NestedSet):
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1) db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
if db_value: if db_value:
if self.report_type != db_value.report_type: if self.report_type != db_value.report_type:
frappe.db.sql( frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
"update `tabAccount` set report_type=%s where lft > %s and rgt < %s", (self.report_type, self.lft, self.rgt))
(self.report_type, self.lft, self.rgt),
)
if self.root_type != db_value.root_type: if self.root_type != db_value.root_type:
frappe.db.sql( frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
"update `tabAccount` set root_type=%s where lft > %s and rgt < %s", (self.root_type, self.lft, self.rgt))
(self.root_type, self.lft, self.rgt),
)
if self.root_type and not self.report_type: if self.root_type and not self.report_type:
self.report_type = ( self.report_type = "Balance Sheet" \
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
)
def validate_root_details(self): def validate_root_details(self):
# does not exists parent # does not exists parent
@@ -117,44 +89,34 @@ class Account(NestedSet):
throw(_("Root cannot be edited."), RootNotEditable) throw(_("Root cannot be edited."), RootNotEditable)
if not self.parent_account and not self.is_group: if not self.parent_account and not self.is_group:
frappe.throw(_("The root account {0} must be a group").format(frappe.bold(self.name))) frappe.throw(_("Root Account must be a group"))
def validate_root_company_and_sync_account_to_children(self): def validate_root_company_and_sync_account_to_children(self):
# ignore validation while creating new compnay or while syncing to child companies # ignore validation while creating new compnay or while syncing to child companies
if ( if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation:
frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation
):
return return
ancestors = get_root_company(self.company) ancestors = get_root_company(self.company)
if ancestors: if ancestors:
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"): if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
return return
if not frappe.db.get_value(
"Account", {"account_name": self.account_name, "company": ancestors[0]}, "name" if not frappe.db.get_value("Account",
): {'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0])) frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
elif self.parent_account: else:
descendants = get_descendants_of("Company", self.company) descendants = get_descendants_of('Company', self.company)
if not descendants: if not descendants: return
return
parent_acc_name_map = {} parent_acc_name_map = {}
parent_acc_name, parent_acc_number = frappe.db.get_value( parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
"Account", self.parent_account, ["account_name", "account_number"] ["account_name", "account_number"])
) for d in frappe.db.get_values('Account',
filters = { { "company": ["in", descendants], "account_name": parent_acc_name,
"company": ["in", descendants], "account_number": parent_acc_number },
"account_name": parent_acc_name, ["company", "name"], as_dict=True):
}
if parent_acc_number:
filters["account_number"] = parent_acc_number
for d in frappe.db.get_values(
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
):
parent_acc_name_map[d["company"]] = d["name"] parent_acc_name_map[d["company"]] = d["name"]
if not parent_acc_name_map: return
if not parent_acc_name_map:
return
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name) self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
@@ -175,92 +137,65 @@ class Account(NestedSet):
def validate_frozen_accounts_modifier(self): def validate_frozen_accounts_modifier(self):
old_value = frappe.db.get_value("Account", self.name, "freeze_account") old_value = frappe.db.get_value("Account", self.name, "freeze_account")
if old_value and old_value != self.freeze_account: if old_value and old_value != self.freeze_account:
frozen_accounts_modifier = frappe.db.get_value( frozen_accounts_modifier = frappe.db.get_value('Accounts Settings', None, 'frozen_accounts_modifier')
"Accounts Settings", None, "frozen_accounts_modifier" if not frozen_accounts_modifier or \
) frozen_accounts_modifier not in frappe.get_roles():
if not frozen_accounts_modifier or frozen_accounts_modifier not in frappe.get_roles(): throw(_("You are not authorized to set Frozen value"))
throw(_("You are not authorized to set Frozen value"))
def validate_balance_must_be_debit_or_credit(self): def validate_balance_must_be_debit_or_credit(self):
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
if not self.get("__islocal") and self.balance_must_be: if not self.get("__islocal") and self.balance_must_be:
account_balance = get_balance_on(self.name) account_balance = get_balance_on(self.name)
if account_balance > 0 and self.balance_must_be == "Credit": if account_balance > 0 and self.balance_must_be == "Credit":
frappe.throw( frappe.throw(_("Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"))
_(
"Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"
)
)
elif account_balance < 0 and self.balance_must_be == "Debit": elif account_balance < 0 and self.balance_must_be == "Debit":
frappe.throw( frappe.throw(_("Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"))
_(
"Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"
)
)
def validate_account_currency(self): def validate_account_currency(self):
if not self.account_currency: if not self.account_currency:
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency") self.account_currency = frappe.get_cached_value('Company', self.company, "default_currency")
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency") elif self.account_currency != frappe.db.get_value("Account", self.name, "account_currency"):
if gl_currency and self.account_currency != gl_currency:
if frappe.db.get_value("GL Entry", {"account": self.name}): if frappe.db.get_value("GL Entry", {"account": self.name}):
frappe.throw(_("Currency can not be changed after making entries using some other currency")) frappe.throw(_("Currency can not be changed after making entries using some other currency"))
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name): def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
for company in descendants: for company in descendants:
company_bold = frappe.bold(company)
parent_acc_name_bold = frappe.bold(parent_acc_name)
if not parent_acc_name_map.get(company): if not parent_acc_name_map.get(company):
frappe.throw( frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
_( .format(company, parent_acc_name))
"While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA"
).format(company_bold, parent_acc_name_bold),
title=_("Account Not Found"),
)
# validate if parent of child company account to be added is a group filters = {
if frappe.db.get_value("Account", self.parent_account, "is_group") and not frappe.db.get_value( "account_name": self.account_name,
"Account", parent_acc_name_map[company], "is_group" "company": company
): }
msg = _(
"While creating account for Child Company {0}, parent account {1} found as a ledger account."
).format(company_bold, parent_acc_name_bold)
msg += "<br><br>"
msg += _(
"Please convert the parent account in corresponding child company to a group account."
)
frappe.throw(msg, title=_("Invalid Parent Account"))
filters = {"account_name": self.account_name, "company": company}
if self.account_number: if self.account_number:
filters["account_number"] = self.account_number filters["account_number"] = self.account_number
child_account = frappe.db.get_value("Account", filters, "name") child_account = frappe.db.get_value("Account", filters, 'name')
if not child_account: if not child_account:
doc = frappe.copy_doc(self) doc = frappe.copy_doc(self)
doc.flags.ignore_root_company_validation = True doc.flags.ignore_root_company_validation = True
doc.update( doc.update({
{ "company": company,
"company": company, # parent account's currency should be passed down to child account's curreny
# parent account's currency should be passed down to child account's curreny # if it is None, it picks it up from default company currency, which might be unintended
# if it is None, it picks it up from default company currency, which might be unintended "account_currency": self.account_currency,
"account_currency": erpnext.get_company_currency(company), "parent_account": parent_acc_name_map[company]
"parent_account": parent_acc_name_map[company], })
}
)
doc.save() doc.save()
frappe.msgprint(_("Account {0} is added in the child company {1}").format(doc.name, company)) frappe.msgprint(_("Account {0} is added in the child company {1}")
.format(doc.name, company))
elif child_account: elif child_account:
# update the parent company's value in child companies # update the parent company's value in child companies
doc = frappe.get_doc("Account", child_account) doc = frappe.get_doc("Account", child_account)
parent_value_changed = False parent_value_changed = False
for field in ["account_type", "freeze_account", "balance_must_be"]: for field in ['account_type', 'account_currency',
'freeze_account', 'balance_must_be']:
if doc.get(field) != self.get(field): if doc.get(field) != self.get(field):
parent_value_changed = True parent_value_changed = True
doc.set(field, self.get(field)) doc.set(field, self.get(field))
@@ -268,7 +203,6 @@ class Account(NestedSet):
if parent_value_changed: if parent_value_changed:
doc.save() doc.save()
@frappe.whitelist()
def convert_group_to_ledger(self): def convert_group_to_ledger(self):
if self.check_if_child_exists(): if self.check_if_child_exists():
throw(_("Account with child nodes cannot be converted to ledger")) throw(_("Account with child nodes cannot be converted to ledger"))
@@ -279,12 +213,11 @@ class Account(NestedSet):
self.save() self.save()
return 1 return 1
@frappe.whitelist()
def convert_ledger_to_group(self): def convert_ledger_to_group(self):
if self.check_gle_exists(): if self.check_gle_exists():
throw(_("Account with existing transaction can not be converted to group.")) throw(_("Account with existing transaction can not be converted to group."))
elif self.account_type and not self.flags.exclude_account_type_check: elif self.account_type and not self.flags.exclude_account_type_check:
throw(_("Cannot convert to Group because Account Type is selected.")) throw(_("Cannot covert to Group because Account Type is selected."))
else: else:
self.is_group = 1 self.is_group = 1
self.save() self.save()
@@ -295,11 +228,8 @@ class Account(NestedSet):
return frappe.db.get_value("GL Entry", {"account": self.name}) return frappe.db.get_value("GL Entry", {"account": self.name})
def check_if_child_exists(self): def check_if_child_exists(self):
return frappe.db.sql( return frappe.db.sql("""select name from `tabAccount` where parent_account = %s
"""select name from `tabAccount` where parent_account = %s and docstatus != 2""", self.name)
and docstatus != 2""",
self.name,
)
def validate_mandatory(self): def validate_mandatory(self):
if not self.root_type: if not self.root_type:
@@ -315,102 +245,53 @@ class Account(NestedSet):
super(Account, self).on_trash(True) super(Account, self).on_trash(True)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_parent_account(doctype, txt, searchfield, start, page_len, filters): def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql( return frappe.db.sql("""select name from tabAccount
"""select name from tabAccount
where is_group = 1 and docstatus != 2 and company = %s where is_group = 1 and docstatus != 2 and company = %s
and %s like %s order by name limit %s, %s""" and %s like %s order by name limit %s, %s""" %
% ("%s", searchfield, "%s", "%s", "%s"), ("%s", searchfield, "%s", "%s", "%s"),
(filters["company"], "%%%s%%" % txt, start, page_len), (filters["company"], "%%%s%%" % txt, start, page_len), as_list=1)
as_list=1,
)
def get_account_currency(account): def get_account_currency(account):
"""Helper function to get account currency""" """Helper function to get account currency"""
if not account: if not account:
return return
def generator(): def generator():
account_currency, company = frappe.get_cached_value( account_currency, company = frappe.get_cached_value("Account", account, ["account_currency", "company"])
"Account", account, ["account_currency", "company"]
)
if not account_currency: if not account_currency:
account_currency = frappe.get_cached_value("Company", company, "default_currency") account_currency = frappe.get_cached_value('Company', company, "default_currency")
return account_currency return account_currency
return frappe.local_cache("account_currency", account, generator) return frappe.local_cache("account_currency", account, generator)
def on_doctype_update(): def on_doctype_update():
frappe.db.add_index("Account", ["lft", "rgt"]) frappe.db.add_index("Account", ["lft", "rgt"])
def get_account_autoname(account_number, account_name, company): def get_account_autoname(account_number, account_name, company):
# first validate if company exists # first validate if company exists
company = frappe.get_cached_value("Company", company, ["abbr", "name"], as_dict=True) company = frappe.get_cached_value('Company', company, ["abbr", "name"], as_dict=True)
if not company: if not company:
frappe.throw(_("Company {0} does not exist").format(company)) frappe.throw(_('Company {0} does not exist').format(company))
parts = [account_name.strip(), company.abbr] parts = [account_name.strip(), company.abbr]
if cstr(account_number).strip(): if cstr(account_number).strip():
parts.insert(0, cstr(account_number).strip()) parts.insert(0, cstr(account_number).strip())
return " - ".join(parts) return ' - '.join(parts)
def validate_account_number(name, account_number, company): def validate_account_number(name, account_number, company):
if account_number: if account_number:
account_with_same_number = frappe.db.get_value( account_with_same_number = frappe.db.get_value("Account",
"Account", {"account_number": account_number, "company": company, "name": ["!=", name]} {"account_number": account_number, "company": company, "name": ["!=", name]})
)
if account_with_same_number: if account_with_same_number:
frappe.throw( frappe.throw(_("Account Number {0} already used in account {1}")
_("Account Number {0} already used in account {1}").format( .format(account_number, account_with_same_number))
account_number, account_with_same_number
)
)
@frappe.whitelist() @frappe.whitelist()
def update_account_number(name, account_name, account_number=None, from_descendant=False): def update_account_number(name, account_name, account_number=None):
account = frappe.db.get_value("Account", name, "company", as_dict=True) account = frappe.db.get_value("Account", name, "company", as_dict=True)
if not account: if not account: return
return
old_acc_name, old_acc_number = frappe.db.get_value(
"Account", name, ["account_name", "account_number"]
)
# check if account exists in parent company
ancestors = get_ancestors_of("Company", account.company)
allow_independent_account_creation = frappe.get_value(
"Company", account.company, "allow_account_creation_against_child_company"
)
if ancestors and not allow_independent_account_creation:
for ancestor in ancestors:
if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"):
# same account in parent company exists
allow_child_account_creation = _("Allow Account Creation Against Child Company")
message = _("Account {0} exists in parent company {1}.").format(
frappe.bold(old_acc_name), frappe.bold(ancestor)
)
message += "<br>"
message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(
frappe.bold(ancestor)
)
message += "<br><br>"
message += _("To overrule this, enable '{0}' in company {1}").format(
allow_child_account_creation, frappe.bold(account.company)
)
frappe.throw(message, title=_("Rename Not Allowed"))
validate_account_number(name, account_number, account.company) validate_account_number(name, account_number, account.company)
if account_number: if account_number:
frappe.db.set_value("Account", name, "account_number", account_number.strip()) frappe.db.set_value("Account", name, "account_number", account_number.strip())
@@ -418,63 +299,33 @@ def update_account_number(name, account_name, account_number=None, from_descenda
frappe.db.set_value("Account", name, "account_number", "") frappe.db.set_value("Account", name, "account_number", "")
frappe.db.set_value("Account", name, "account_name", account_name.strip()) frappe.db.set_value("Account", name, "account_name", account_name.strip())
if not from_descendant:
# Update and rename in child company accounts as well
descendants = get_descendants_of("Company", account.company)
if descendants:
sync_update_account_number_in_child(
descendants, old_acc_name, account_name, account_number, old_acc_number
)
new_name = get_account_autoname(account_number, account_name, account.company) new_name = get_account_autoname(account_number, account_name, account.company)
if name != new_name: if name != new_name:
frappe.rename_doc("Account", name, new_name, force=1) frappe.rename_doc("Account", name, new_name, force=1)
return new_name return new_name
@frappe.whitelist() @frappe.whitelist()
def merge_account(old, new, is_group, root_type, company): def merge_account(old, new, is_group, root_type, company):
# Validate properties before merging # Validate properties before merging
if not frappe.db.exists("Account", new): if not frappe.db.exists("Account", new):
throw(_("Account {0} does not exist").format(new)) throw(_("Account {0} does not exist").format(new))
val = list(frappe.db.get_value("Account", new, ["is_group", "root_type", "company"])) val = list(frappe.db.get_value("Account", new,
["is_group", "root_type", "company"]))
if val != [cint(is_group), root_type, company]: if val != [cint(is_group), root_type, company]:
throw( throw(_("""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""))
_(
"""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""
)
)
if is_group and frappe.db.get_value("Account", new, "parent_account") == old: if is_group and frappe.db.get_value("Account", new, "parent_account") == old:
frappe.db.set_value( frappe.db.set_value("Account", new, "parent_account",
"Account", new, "parent_account", frappe.db.get_value("Account", old, "parent_account") frappe.db.get_value("Account", old, "parent_account"))
)
frappe.rename_doc("Account", old, new, merge=1, force=1) frappe.rename_doc("Account", old, new, merge=1, force=1)
return new return new
@frappe.whitelist() @frappe.whitelist()
def get_root_company(company): def get_root_company(company):
# return the topmost company in the hierarchy # return the topmost company in the hierarchy
ancestors = get_ancestors_of("Company", company, "lft asc") ancestors = get_ancestors_of('Company', company, "lft asc")
return [ancestors[0]] if ancestors else [] return [ancestors[0]] if ancestors else []
def sync_update_account_number_in_child(
descendants, old_acc_name, account_name, account_number=None, old_acc_number=None
):
filters = {
"company": ["in", descendants],
"account_name": old_acc_name,
}
if old_acc_number:
filters["account_number"] = old_acc_number
for d in frappe.db.get_values(
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
):
update_account_number(d["name"], account_name, account_number, from_descendant=True)

View File

@@ -1,8 +1,8 @@
frappe.provide("frappe.treeview_settings") frappe.provide("frappe.treeview_settings")
frappe.treeview_settings["Account"] = { frappe.treeview_settings["Account"] = {
breadcrumb: "Accounts", breadcrumbs: "Accounts",
title: __("Chart of Accounts"), title: __("Chart Of Accounts"),
get_tree_root: false, get_tree_root: false,
filters: [ filters: [
{ {
@@ -14,9 +14,6 @@ frappe.treeview_settings["Account"] = {
on_change: function() { on_change: function() {
var me = frappe.treeview_settings['Account'].treeview; var me = frappe.treeview_settings['Account'].treeview;
var company = me.page.fields_dict.company.get_value(); var company = me.page.fields_dict.company.get_value();
if (!company) {
frappe.throw(__("Please set a Company"));
}
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.account.account.get_root_company", method: "erpnext.accounts.doctype.account.account.get_root_company",
args: { args: {
@@ -45,50 +42,6 @@ frappe.treeview_settings["Account"] = {
], ],
root_label: "Accounts", root_label: "Accounts",
get_tree_nodes: 'erpnext.accounts.utils.get_children', get_tree_nodes: 'erpnext.accounts.utils.get_children',
on_get_node: function(nodes, deep=false) {
if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return;
let accounts = [];
if (deep) {
// in case of `get_all_nodes`
accounts = nodes.reduce((acc, node) => [...acc, ...node.data], []);
} else {
accounts = nodes;
}
const get_balances = frappe.call({
method: 'erpnext.accounts.utils.get_account_balances',
args: {
accounts: accounts,
company: cur_tree.args.company
},
});
get_balances.then(r => {
if (!r.message || r.message.length == 0) return;
for (let account of r.message) {
const node = cur_tree.nodes && cur_tree.nodes[account.value];
if (!node || node.is_root) continue;
// show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? "Dr": "Cr";
const format = (value, currency) => format_currency(Math.abs(value), currency);
if (account.balance!==undefined) {
node.parent && node.parent.find('.balance-area').remove();
$('<span class="balance-area pull-right">'
+ (account.balance_in_account_currency ?
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
+ format(account.balance, account.company_currency)
+ " " + dr_or_cr
+ '</span>').insertBefore(node.$ul);
}
}
});
},
add_tree_node: 'erpnext.accounts.utils.add_ac', add_tree_node: 'erpnext.accounts.utils.add_ac',
menu_items:[ menu_items:[
{ {
@@ -141,7 +94,7 @@ frappe.treeview_settings["Account"] = {
treeview.page.add_inner_button(__("Journal Entry"), function() { treeview.page.add_inner_button(__("Journal Entry"), function() {
frappe.new_doc('Journal Entry', {company: get_company()}); frappe.new_doc('Journal Entry', {company: get_company()});
}, __('Create')); }, __('Create'));
treeview.page.add_inner_button(__("Company"), function() { treeview.page.add_inner_button(__("New Company"), function() {
frappe.new_doc('Company'); frappe.new_doc('Company');
}, __('Create')); }, __('Create'));
@@ -164,7 +117,25 @@ frappe.treeview_settings["Account"] = {
} else { } else {
treeview.new_node(); treeview.new_node();
} }
}, "add"); }, "octicon octicon-plus");
},
onrender: function(node) {
if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){
// show Dr if positive since balance is calculated as debit - credit else show Cr
let balance = node.data.balance_in_account_currency || node.data.balance;
let dr_or_cr = balance > 0 ? "Dr": "Cr";
if (node.data && node.data.balance!==undefined) {
$('<span class="balance-area pull-right text-muted small">'
+ (node.data.balance_in_account_currency ?
(format_currency(Math.abs(node.data.balance_in_account_currency),
node.data.account_currency) + " / ") : "")
+ format_currency(Math.abs(node.data.balance), node.data.company_currency)
+ " " + dr_or_cr
+ '</span>').insertBefore(node.$ul);
}
}
}, },
toolbar: [ toolbar: [
{ {
@@ -176,7 +147,7 @@ frappe.treeview_settings["Account"] = {
&& node.expandable && !node.hide_add; && node.expandable && !node.hide_add;
}, },
click: function() { click: function() {
var me = frappe.views.trees['Account']; var me = frappe.treeview_settings['Account'].treeview;
me.new_node(); me.new_node();
}, },
btnClass: "hidden-xs" btnClass: "hidden-xs"

View File

@@ -1,19 +1,15 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import json
import os
import frappe import frappe, os, json
from frappe.utils import cstr from frappe.utils import cstr
from frappe.utils.nestedset import rebuild_tree
from six import iteritems
from unidecode import unidecode from unidecode import unidecode
from six import iteritems
from frappe.utils.nestedset import rebuild_tree
def create_charts(company, chart_template=None, existing_company=None, custom_chart=None):
def create_charts(
company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None
):
chart = custom_chart or get_chart(chart_template, existing_company) chart = custom_chart or get_chart(chart_template, existing_company)
if chart: if chart:
accounts = [] accounts = []
@@ -23,41 +19,30 @@ def create_charts(
if root_account: if root_account:
root_type = child.get("root_type") root_type = child.get("root_type")
if account_name not in [ if account_name not in ["account_number", "account_type",
"account_name", "root_type", "is_group", "tax_rate"]:
"account_number",
"account_type",
"root_type",
"is_group",
"tax_rate",
]:
account_number = cstr(child.get("account_number")).strip() account_number = cstr(child.get("account_number")).strip()
account_name, account_name_in_db = add_suffix_if_duplicate( account_name, account_name_in_db = add_suffix_if_duplicate(account_name,
account_name, account_number, accounts account_number, accounts)
)
is_group = identify_is_group(child) is_group = identify_is_group(child)
report_type = ( report_type = "Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] \
"Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] else "Profit and Loss" else "Profit and Loss"
)
account = frappe.get_doc( account = frappe.get_doc({
{ "doctype": "Account",
"doctype": "Account", "account_name": account_name,
"account_name": child.get("account_name") if from_coa_importer else account_name, "company": company,
"company": company, "parent_account": parent,
"parent_account": parent, "is_group": is_group,
"is_group": is_group, "root_type": root_type,
"root_type": root_type, "report_type": report_type,
"report_type": report_type, "account_number": account_number,
"account_number": account_number, "account_type": child.get("account_type"),
"account_type": child.get("account_type"), "account_currency": child.get('account_currency') or frappe.db.get_value('Company', company, "default_currency"),
"account_currency": child.get("account_currency") "tax_rate": child.get("tax_rate")
or frappe.db.get_value("Company", company, "default_currency"), })
"tax_rate": child.get("tax_rate"),
}
)
if root_account or frappe.local.flags.allow_unverified_charts: if root_account or frappe.local.flags.allow_unverified_charts:
account.flags.ignore_mandatory = True account.flags.ignore_mandatory = True
@@ -72,15 +57,15 @@ def create_charts(
# Rebuild NestedSet HSM tree for Account Doctype # Rebuild NestedSet HSM tree for Account Doctype
# after all accounts are already inserted. # after all accounts are already inserted.
frappe.local.flags.ignore_update_nsm = True frappe.local.flags.ignore_on_update = True
_import_accounts(chart, None, None, root_account=True) _import_accounts(chart, None, None, root_account=True)
rebuild_tree("Account", "parent_account") rebuild_tree("Account", "parent_account")
frappe.local.flags.ignore_update_nsm = False frappe.local.flags.ignore_on_update = False
def add_suffix_if_duplicate(account_name, account_number, accounts): def add_suffix_if_duplicate(account_name, account_number, accounts):
if account_number: if account_number:
account_name_in_db = unidecode(" - ".join([account_number, account_name.strip().lower()])) account_name_in_db = unidecode(" - ".join([account_number,
account_name.strip().lower()]))
else: else:
account_name_in_db = unidecode(account_name.strip().lower()) account_name_in_db = unidecode(account_name.strip().lower())
@@ -90,37 +75,27 @@ def add_suffix_if_duplicate(account_name, account_number, accounts):
return account_name, account_name_in_db return account_name, account_name_in_db
def identify_is_group(child): def identify_is_group(child):
if child.get("is_group"): if child.get("is_group"):
is_group = child.get("is_group") is_group = child.get("is_group")
elif len( elif len(set(child.keys()) - set(["account_type", "root_type", "is_group", "tax_rate", "account_number"])):
set(child.keys())
- set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])
):
is_group = 1 is_group = 1
else: else:
is_group = 0 is_group = 0
return is_group return is_group
def get_chart(chart_template, existing_company=None): def get_chart(chart_template, existing_company=None):
chart = {} chart = {}
if existing_company: if existing_company:
return get_account_tree_from_existing_company(existing_company) return get_account_tree_from_existing_company(existing_company)
elif chart_template == "Standard": elif chart_template == "Standard":
from erpnext.accounts.doctype.account.chart_of_accounts.verified import ( from erpnext.accounts.doctype.account.chart_of_accounts.verified import standard_chart_of_accounts
standard_chart_of_accounts,
)
return standard_chart_of_accounts.get() return standard_chart_of_accounts.get()
elif chart_template == "Standard with Numbers": elif chart_template == "Standard with Numbers":
from erpnext.accounts.doctype.account.chart_of_accounts.verified import ( from erpnext.accounts.doctype.account.chart_of_accounts.verified \
standard_chart_of_accounts_with_account_number, import standard_chart_of_accounts_with_account_number
)
return standard_chart_of_accounts_with_account_number.get() return standard_chart_of_accounts_with_account_number.get()
else: else:
folders = ("verified",) folders = ("verified",)
@@ -136,7 +111,6 @@ def get_chart(chart_template, existing_company=None):
if chart and json.loads(chart).get("name") == chart_template: if chart and json.loads(chart).get("name") == chart_template:
return json.loads(chart).get("tree") return json.loads(chart).get("tree")
@frappe.whitelist() @frappe.whitelist()
def get_charts_for_country(country, with_standard=False): def get_charts_for_country(country, with_standard=False):
charts = [] charts = []
@@ -144,10 +118,9 @@ def get_charts_for_country(country, with_standard=False):
def _get_chart_name(content): def _get_chart_name(content):
if content: if content:
content = json.loads(content) content = json.loads(content)
if ( if (content and content.get("disabled", "No") == "No") \
content and content.get("disabled", "No") == "No" or frappe.local.flags.allow_unverified_charts:
) or frappe.local.flags.allow_unverified_charts: charts.append(content["name"])
charts.append(content["name"])
country_code = frappe.db.get_value("Country", country, "code") country_code = frappe.db.get_value("Country", country, "code")
if country_code: if country_code:
@@ -174,21 +147,11 @@ def get_charts_for_country(country, with_standard=False):
def get_account_tree_from_existing_company(existing_company): def get_account_tree_from_existing_company(existing_company):
all_accounts = frappe.get_all( all_accounts = frappe.get_all('Account',
"Account", filters={'company': existing_company},
filters={"company": existing_company}, fields = ["name", "account_name", "parent_account", "account_type",
fields=[ "is_group", "root_type", "tax_rate", "account_number"],
"name", order_by="lft, rgt")
"account_name",
"parent_account",
"account_type",
"is_group",
"root_type",
"tax_rate",
"account_number",
],
order_by="lft, rgt",
)
account_tree = {} account_tree = {}
@@ -197,7 +160,6 @@ def get_account_tree_from_existing_company(existing_company):
build_account_tree(account_tree, None, all_accounts) build_account_tree(account_tree, None, all_accounts)
return account_tree return account_tree
def build_account_tree(tree, parent, all_accounts): def build_account_tree(tree, parent, all_accounts):
# find children # find children
parent_account = parent.name if parent else "" parent_account = parent.name if parent else ""
@@ -226,29 +188,27 @@ def build_account_tree(tree, parent, all_accounts):
# call recursively to build a subtree for current account # call recursively to build a subtree for current account
build_account_tree(tree[child.account_name], child, all_accounts) build_account_tree(tree[child.account_name], child, all_accounts)
@frappe.whitelist() @frappe.whitelist()
def validate_bank_account(coa, bank_account): def validate_bank_account(coa, bank_account):
accounts = [] accounts = []
chart = get_chart(coa) chart = get_chart(coa)
if chart: if chart:
def _get_account_names(account_master): def _get_account_names(account_master):
for account_name, child in iteritems(account_master): for account_name, child in iteritems(account_master):
if account_name not in ["account_number", "account_type", "root_type", "is_group", "tax_rate"]: if account_name not in ["account_number", "account_type",
"root_type", "is_group", "tax_rate"]:
accounts.append(account_name) accounts.append(account_name)
_get_account_names(child) _get_account_names(child)
_get_account_names(chart) _get_account_names(chart)
return bank_account in accounts return (bank_account in accounts)
@frappe.whitelist() @frappe.whitelist()
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False): def build_tree_from_json(chart_template, chart_data=None):
"""get chart template from its folder and parse the json to be rendered as tree""" ''' get chart template from its folder and parse the json to be rendered as tree '''
chart = chart_data or get_chart(chart_template) chart = chart_data or get_chart(chart_template)
# if no template selected, return as it is # if no template selected, return as it is
@@ -256,33 +216,19 @@ def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=Fals
return return
accounts = [] accounts = []
def _import_accounts(children, parent): def _import_accounts(children, parent):
"""recursively called to form a parent-child based list of dict from chart template""" ''' recursively called to form a parent-child based list of dict from chart template '''
for account_name, child in iteritems(children): for account_name, child in iteritems(children):
account = {} account = {}
if account_name in [ if account_name in ["account_number", "account_type",\
"account_name", "root_type", "is_group", "tax_rate"]: continue
"account_number",
"account_type",
"root_type",
"is_group",
"tax_rate",
]:
continue
if from_coa_importer: account['parent_account'] = parent
account_name = child["account_name"] account['expandable'] = True if identify_is_group(child) else False
account['value'] = (child.get('account_number') + ' - ' + account_name) \
account["parent_account"] = parent if child.get('account_number') else account_name
account["expandable"] = True if identify_is_group(child) else False
account["value"] = (
(cstr(child.get("account_number")).strip() + " - " + account_name)
if child.get("account_number")
else account_name
)
accounts.append(account) accounts.append(account)
_import_accounts(child, account["value"]) _import_accounts(child, account['value'])
_import_accounts(chart, None) _import_accounts(chart, None)
return accounts return accounts

View File

@@ -4,14 +4,14 @@
""" """
Import chart of accounts from OpenERP sources Import chart of accounts from OpenERP sources
""" """
from __future__ import print_function, unicode_literals
import os, json
import ast import ast
import json
import os
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
import frappe
from frappe.utils.csvutils import read_csv_content from frappe.utils.csvutils import read_csv_content
import frappe
from six import iteritems from six import iteritems
path = "/Users/nabinhait/projects/odoo/addons" path = "/Users/nabinhait/projects/odoo/addons"
@@ -21,7 +21,6 @@ charts = {}
all_account_types = [] all_account_types = []
all_roots = {} all_roots = {}
def go(): def go():
global accounts, charts global accounts, charts
default_account_types = get_default_account_types() default_account_types = get_default_account_types()
@@ -36,16 +35,14 @@ def go():
accounts, charts = {}, {} accounts, charts = {}, {}
country_path = os.path.join(path, country_dir) country_path = os.path.join(path, country_dir)
manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read()) manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read())
data_files = ( data_files = manifest.get("data", []) + manifest.get("init_xml", []) + \
manifest.get("data", []) + manifest.get("init_xml", []) + manifest.get("update_xml", []) manifest.get("update_xml", [])
)
files_path = [os.path.join(country_path, d) for d in data_files] files_path = [os.path.join(country_path, d) for d in data_files]
xml_roots = get_xml_roots(files_path) xml_roots = get_xml_roots(files_path)
csv_content = get_csv_contents(files_path) csv_content = get_csv_contents(files_path)
prefix = country_dir if csv_content else None prefix = country_dir if csv_content else None
account_types = get_account_types( account_types = get_account_types(xml_roots.get("account.account.type", []),
xml_roots.get("account.account.type", []), csv_content.get("account.account.type", []), prefix csv_content.get("account.account.type", []), prefix)
)
account_types.update(default_account_types) account_types.update(default_account_types)
if xml_roots: if xml_roots:
@@ -58,15 +55,12 @@ def go():
create_all_roots_file() create_all_roots_file()
def get_default_account_types(): def get_default_account_types():
default_types_root = [] default_types_root = []
default_types_root.append( default_types_root.append(ET.parse(os.path.join(path, "account", "data",
ET.parse(os.path.join(path, "account", "data", "data_account_type.xml")).getroot() "data_account_type.xml")).getroot())
)
return get_account_types(default_types_root, None, prefix="account") return get_account_types(default_types_root, None, prefix="account")
def get_xml_roots(files_path): def get_xml_roots(files_path):
xml_roots = frappe._dict() xml_roots = frappe._dict()
for filepath in files_path: for filepath in files_path:
@@ -75,69 +69,64 @@ def get_xml_roots(files_path):
tree = ET.parse(filepath) tree = ET.parse(filepath)
root = tree.getroot() root = tree.getroot()
for node in root[0].findall("record"): for node in root[0].findall("record"):
if node.get("model") in [ if node.get("model") in ["account.account.template",
"account.account.template", "account.chart.template", "account.account.type"]:
"account.chart.template",
"account.account.type",
]:
xml_roots.setdefault(node.get("model"), []).append(root) xml_roots.setdefault(node.get("model"), []).append(root)
break break
return xml_roots return xml_roots
def get_csv_contents(files_path): def get_csv_contents(files_path):
csv_content = {} csv_content = {}
for filepath in files_path: for filepath in files_path:
fname = os.path.basename(filepath) fname = os.path.basename(filepath)
for file_type in ["account.account.template", "account.account.type", "account.chart.template"]: for file_type in ["account.account.template", "account.account.type",
"account.chart.template"]:
if fname.startswith(file_type) and fname.endswith(".csv"): if fname.startswith(file_type) and fname.endswith(".csv"):
with open(filepath, "r") as csvfile: with open(filepath, "r") as csvfile:
try: try:
csv_content.setdefault(file_type, []).append(read_csv_content(csvfile.read())) csv_content.setdefault(file_type, [])\
.append(read_csv_content(csvfile.read()))
except Exception as e: except Exception as e:
continue continue
return csv_content return csv_content
def get_account_types(root_list, csv_content, prefix=None): def get_account_types(root_list, csv_content, prefix=None):
types = {} types = {}
account_type_map = { account_type_map = {
"cash": "Cash", 'cash': 'Cash',
"bank": "Bank", 'bank': 'Bank',
"tr_cash": "Cash", 'tr_cash': 'Cash',
"tr_bank": "Bank", 'tr_bank': 'Bank',
"receivable": "Receivable", 'receivable': 'Receivable',
"tr_receivable": "Receivable", 'tr_receivable': 'Receivable',
"account rec": "Receivable", 'account rec': 'Receivable',
"payable": "Payable", 'payable': 'Payable',
"tr_payable": "Payable", 'tr_payable': 'Payable',
"equity": "Equity", 'equity': 'Equity',
"stocks": "Stock", 'stocks': 'Stock',
"stock": "Stock", 'stock': 'Stock',
"tax": "Tax", 'tax': 'Tax',
"tr_tax": "Tax", 'tr_tax': 'Tax',
"tax-out": "Tax", 'tax-out': 'Tax',
"tax-in": "Tax", 'tax-in': 'Tax',
"charges_personnel": "Chargeable", 'charges_personnel': 'Chargeable',
"fixed asset": "Fixed Asset", 'fixed asset': 'Fixed Asset',
"cogs": "Cost of Goods Sold", 'cogs': 'Cost of Goods Sold',
} }
for root in root_list: for root in root_list:
for node in root[0].findall("record"): for node in root[0].findall("record"):
if node.get("model") == "account.account.type": if node.get("model")=="account.account.type":
data = {} data = {}
for field in node.findall("field"): for field in node.findall("field"):
if ( if field.get("name")=="code" and field.text.lower() != "none" \
field.get("name") == "code" and account_type_map.get(field.text):
and field.text.lower() != "none" data["account_type"] = account_type_map[field.text]
and account_type_map.get(field.text)
):
data["account_type"] = account_type_map[field.text]
node_id = prefix + "." + node.get("id") if prefix else node.get("id") node_id = prefix + "." + node.get("id") if prefix else node.get("id")
types[node_id] = data types[node_id] = data
if csv_content and csv_content[0][0] == "id": if csv_content and csv_content[0][0]=="id":
for row in csv_content[1:]: for row in csv_content[1:]:
row_dict = dict(zip(csv_content[0], row)) row_dict = dict(zip(csv_content[0], row))
data = {} data = {}
@@ -148,22 +137,21 @@ def get_account_types(root_list, csv_content, prefix=None):
types[node_id] = data types[node_id] = data
return types return types
def make_maps_for_xml(xml_roots, account_types, country_dir): def make_maps_for_xml(xml_roots, account_types, country_dir):
"""make maps for `charts` and `accounts`""" """make maps for `charts` and `accounts`"""
for model, root_list in iteritems(xml_roots): for model, root_list in iteritems(xml_roots):
for root in root_list: for root in root_list:
for node in root[0].findall("record"): for node in root[0].findall("record"):
if node.get("model") == "account.account.template": if node.get("model")=="account.account.template":
data = {} data = {}
for field in node.findall("field"): for field in node.findall("field"):
if field.get("name") == "name": if field.get("name")=="name":
data["name"] = field.text data["name"] = field.text
if field.get("name") == "parent_id": if field.get("name")=="parent_id":
parent_id = field.get("ref") or field.get("eval") parent_id = field.get("ref") or field.get("eval")
data["parent_id"] = parent_id data["parent_id"] = parent_id
if field.get("name") == "user_type": if field.get("name")=="user_type":
value = field.get("ref") value = field.get("ref")
if account_types.get(value, {}).get("account_type"): if account_types.get(value, {}).get("account_type"):
data["account_type"] = account_types[value]["account_type"] data["account_type"] = account_types[value]["account_type"]
@@ -173,17 +161,16 @@ def make_maps_for_xml(xml_roots, account_types, country_dir):
data["children"] = [] data["children"] = []
accounts[node.get("id")] = data accounts[node.get("id")] = data
if node.get("model") == "account.chart.template": if node.get("model")=="account.chart.template":
data = {} data = {}
for field in node.findall("field"): for field in node.findall("field"):
if field.get("name") == "name": if field.get("name")=="name":
data["name"] = field.text data["name"] = field.text
if field.get("name") == "account_root_id": if field.get("name")=="account_root_id":
data["account_root_id"] = field.get("ref") data["account_root_id"] = field.get("ref")
data["id"] = country_dir data["id"] = country_dir
charts.setdefault(node.get("id"), {}).update(data) charts.setdefault(node.get("id"), {}).update(data)
def make_maps_for_csv(csv_content, account_types, country_dir): def make_maps_for_csv(csv_content, account_types, country_dir):
for content in csv_content.get("account.account.template", []): for content in csv_content.get("account.account.template", []):
for row in content[1:]: for row in content[1:]:
@@ -191,7 +178,7 @@ def make_maps_for_csv(csv_content, account_types, country_dir):
account = { account = {
"name": data.get("name"), "name": data.get("name"),
"parent_id": data.get("parent_id:id") or data.get("parent_id/id"), "parent_id": data.get("parent_id:id") or data.get("parent_id/id"),
"children": [], "children": []
} }
user_type = data.get("user_type/id") or data.get("user_type:id") user_type = data.get("user_type/id") or data.get("user_type:id")
if account_types.get(user_type, {}).get("account_type"): if account_types.get(user_type, {}).get("account_type"):
@@ -208,14 +195,12 @@ def make_maps_for_csv(csv_content, account_types, country_dir):
for row in content[1:]: for row in content[1:]:
if row: if row:
data = dict(zip(content[0], row)) data = dict(zip(content[0], row))
charts.setdefault(data.get("id"), {}).update( charts.setdefault(data.get("id"), {}).update({
{ "account_root_id": data.get("account_root_id:id") or \
"account_root_id": data.get("account_root_id:id") or data.get("account_root_id/id"), data.get("account_root_id/id"),
"name": data.get("name"), "name": data.get("name"),
"id": country_dir, "id": country_dir
} })
)
def make_account_trees(): def make_account_trees():
"""build tree hierarchy""" """build tree hierarchy"""
@@ -234,7 +219,6 @@ def make_account_trees():
if "children" in accounts[id] and not accounts[id].get("children"): if "children" in accounts[id] and not accounts[id].get("children"):
del accounts[id]["children"] del accounts[id]["children"]
def make_charts(): def make_charts():
"""write chart files in app/setup/doctype/company/charts""" """write chart files in app/setup/doctype/company/charts"""
for chart_id in charts: for chart_id in charts:
@@ -253,38 +237,34 @@ def make_charts():
chart["country_code"] = src["id"][5:] chart["country_code"] = src["id"][5:]
chart["tree"] = accounts[src["account_root_id"]] chart["tree"] = accounts[src["account_root_id"]]
for key, val in chart["tree"].items(): for key, val in chart["tree"].items():
if key in ["name", "parent_id"]: if key in ["name", "parent_id"]:
chart["tree"].pop(key) chart["tree"].pop(key)
if type(val) == dict: if type(val) == dict:
val["root_type"] = "" val["root_type"] = ""
if chart: if chart:
fpath = os.path.join( fpath = os.path.join("erpnext", "erpnext", "accounts", "doctype", "account",
"erpnext", "erpnext", "accounts", "doctype", "account", "chart_of_accounts", filename + ".json" "chart_of_accounts", filename + ".json")
)
with open(fpath, "r") as chartfile: with open(fpath, "r") as chartfile:
old_content = chartfile.read() old_content = chartfile.read()
if not old_content or ( if not old_content or (json.loads(old_content).get("is_active", "No") == "No" \
json.loads(old_content).get("is_active", "No") == "No" and json.loads(old_content).get("disabled", "No") == "No"):
and json.loads(old_content).get("disabled", "No") == "No"
):
with open(fpath, "w") as chartfile: with open(fpath, "w") as chartfile:
chartfile.write(json.dumps(chart, indent=4, sort_keys=True)) chartfile.write(json.dumps(chart, indent=4, sort_keys=True))
all_roots.setdefault(filename, chart["tree"].keys()) all_roots.setdefault(filename, chart["tree"].keys())
def create_all_roots_file(): def create_all_roots_file():
with open("all_roots.txt", "w") as f: with open('all_roots.txt', 'w') as f:
for filename, roots in sorted(all_roots.items()): for filename, roots in sorted(all_roots.items()):
f.write(filename) f.write(filename)
f.write("\n----------------------\n") f.write('\n----------------------\n')
for r in sorted(roots): for r in sorted(roots):
f.write(r.encode("utf-8")) f.write(r.encode('utf-8'))
f.write("\n") f.write('\n')
f.write("\n\n\n") f.write('\n\n\n')
if __name__=="__main__":
if __name__ == "__main__":
go() go()

View File

@@ -82,7 +82,9 @@
}, },
"Inventory": { "Inventory": {
"Consigned Stock": { "Consigned Stock": {
"Handling Difference in Inventory": {}, "Handling Difference in Inventory": {
"account_type": "Stock Adjustment"
},
"Items Delivered to Customs on temporary Base": {} "Items Delivered to Customs on temporary Base": {}
}, },
"Stock in Hand": { "Stock in Hand": {
@@ -188,9 +190,6 @@
}, },
"Expenses Included In Valuation": { "Expenses Included In Valuation": {
"account_type": "Expenses Included In Valuation" "account_type": "Expenses Included In Valuation"
},
"Stock Adjustment": {
"account_type": "Stock Adjustment"
} }
}, },
"Depreciation": { "Depreciation": {

View File

@@ -1,531 +0,0 @@
{
"country_code": "de",
"name": "SKR03 mit Kontonummern",
"tree": {
"Aktiva": {
"is_group": 1,
"root_type": "Asset",
"A - Anlagevermögen": {
"is_group": 1,
"EDV-Software": {
"account_number": "0027",
"account_type": "Fixed Asset"
},
"Gesch\u00e4ftsausstattung": {
"account_number": "0410",
"account_type": "Fixed Asset"
},
"B\u00fcroeinrichtung": {
"account_number": "0420",
"account_type": "Fixed Asset"
},
"Darlehen": {
"account_number": "0565"
},
"Maschinen": {
"account_number": "0210",
"account_type": "Fixed Asset"
},
"Betriebsausstattung": {
"account_number": "0400",
"account_type": "Fixed Asset"
},
"Ladeneinrichtung": {
"account_number": "0430",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation": {
"account_type": "Accumulated Depreciation"
}
},
"B - Umlaufvermögen": {
"is_group": 1,
"I. Vorräte": {
"is_group": 1,
"Roh-, Hilfs- und Betriebsstoffe (Bestand)": {
"account_number": "3970",
"account_type": "Stock"
},
"Waren (Bestand)": {
"account_number": "3980",
"account_type": "Stock"
}
},
"II. Forderungen und sonstige Vermögensgegenstände": {
"is_group": 1,
"Ford. a. Lieferungen und Leistungen": {
"account_number": "1400",
"account_type": "Receivable"
},
"Durchlaufende Posten": {
"account_number": "1590"
},
"Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
"account_number": "1371"
},
"Abziehbare Vorsteuer": {
"account_type": "Tax",
"is_group": 1,
"Abziehbare Vorsteuer 7%": {
"account_number": "1571"
},
"Abziehbare Vorsteuer 19%": {
"account_number": "1576"
},
"Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
"account_number": "1577"
},
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
"account_number": "3120"
}
}
},
"III. Wertpapiere": {
"is_group": 1
},
"IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": {
"is_group": 1,
"Kasse": {
"account_type": "Cash",
"is_group": 1,
"Kasse": {
"is_group": 1,
"account_number": "1000",
"account_type": "Cash"
}
},
"Bank": {
"is_group": 1,
"account_type": "Bank",
"Postbank": {
"account_number": "1100",
"account_type": "Bank"
},
"Bankkonto": {
"account_number": "1200",
"account_type": "Bank"
}
}
}
},
"C - Rechnungsabgrenzungsposten": {
"is_group": 1,
"Aktive Rechnungsabgrenzung": {
"account_number": "0980"
}
},
"D - Aktive latente Steuern": {
"is_group": 1,
"Aktive latente Steuern": {
"account_number": "0983"
}
},
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
"is_group": 1
}
},
"Passiva": {
"is_group": 1,
"root_type": "Liability",
"A. Eigenkapital": {
"is_group": 1,
"I. Gezeichnetes Kapital": {
"is_group": 1
},
"II. Kapitalrücklage": {
"is_group": 1
},
"III. Gewinnrücklagen": {
"is_group": 1
},
"IV. Gewinnvortrag/Verlustvortrag": {
"is_group": 1
},
"V. Jahresüberschuß/Jahresfehlbetrag": {
"is_group": 1
}
},
"B. Rückstellungen": {
"is_group": 1,
"I. Rückstellungen für Pensionen und ähnliche Verpflichtungen": {
"is_group": 1
},
"II. Steuerrückstellungen": {
"is_group": 1
},
"III. sonstige Rückstellungen": {
"is_group": 1
}
},
"C. Verbindlichkeiten": {
"is_group": 1,
"I. Anleihen": {
"is_group": 1
},
"II. Verbindlichkeiten gegenüber Kreditinstituten": {
"is_group": 1
},
"III. Erhaltene Anzahlungen auf Bestellungen": {
"is_group": 1
},
"IV. Verbindlichkeiten aus Lieferungen und Leistungen": {
"is_group": 1,
"Verbindlichkeiten aus Lieferungen u. Leistungen": {
"account_number": "1600",
"account_type": "Payable"
}
},
"V. Verbindlichkeiten aus der Annahme gezogener Wechsel und der Ausstellung eigener Wechsel": {
"is_group": 1
},
"VI. Verbindlichkeiten gegenüber verbundenen Unternehmen": {
"is_group": 1
},
"VII. Verbindlichkeiten gegenüber Unternehmen, mit denen ein Beteiligungsverhältnis besteht": {
"is_group": 1
},
"VIII. sonstige Verbindlichkeiten": {
"is_group": 1,
"Sonstige Verbindlichkeiten": {
"account_number": "1700",
"account_type": "Asset Received But Not Billed"
},
"Sonstige Verbindlichkeiten (1 bis 5 Jahre)": {
"account_number": "1702",
"account_type": "Stock Received But Not Billed"
},
"Verbindlichkeiten aus Lohn und Gehalt": {
"account_number": "1740",
"account_type": "Payable"
},
"Umsatzsteuer": {
"is_group": 1,
"account_type": "Tax",
"Umsatzsteuer 7%": {
"account_number": "1771"
},
"Umsatzsteuer 19%": {
"account_number": "1776"
},
"Umsatzsteuer-Vorauszahlung": {
"account_number": "1780"
},
"Umsatzsteuer-Vorauszahlung 1/11": {
"account_number": "1781"
},
"Umsatzsteuer \u00a7 13b UStG 19%": {
"account_number": "1787"
},
"Umsatzsteuer Vorjahr": {
"account_number": "1790"
},
"Umsatzsteuer fr\u00fchere Jahre": {
"account_number": "1791"
}
}
}
},
"D. Rechnungsabgrenzungsposten": {
"is_group": 1,
"Passive Rechnungsabgrenzung": {
"account_number": "0990"
}
},
"E. Passive latente Steuern": {
"is_group": 1
}
},
"Erl\u00f6se u. Ertr\u00e4ge 2/8": {
"is_group": 1,
"root_type": "Income",
"Erl\u00f6skonten 8": {
"is_group": 1,
"Erl\u00f6se": {
"account_number": "8200",
"account_type": "Income Account"
},
"Erl\u00f6se USt. 19%": {
"account_number": "8400",
"account_type": "Income Account"
},
"Erl\u00f6se USt. 7%": {
"account_number": "8300",
"account_type": "Income Account"
}
},
"Ertragskonten 2": {
"is_group": 1,
"sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {
"account_number": "2650",
"account_type": "Income Account"
},
"Au\u00dferordentliche Ertr\u00e4ge": {
"account_number": "2500",
"account_type": "Income Account"
},
"Sonstige Ertr\u00e4ge": {
"account_number": "2700",
"account_type": "Income Account"
}
}
},
"Aufwendungen 2/4": {
"is_group": 1,
"root_type": "Expense",
"Wareneingang": {
"account_number": "3200"
},
"Bezugsnebenkosten": {
"account_number": "3800",
"account_type": "Expenses Included In Asset Valuation"
},
"Herstellungskosten": {
"account_number": "4996",
"account_type": "Cost of Goods Sold"
},
"Verluste aus dem Abgang von Gegenständen des Anlagevermögens": {
"account_number": "2320",
"account_type": "Stock Adjustment"
},
"Verwaltungskosten": {
"account_number": "4997",
"account_type": "Expenses Included In Valuation"
},
"Vertriebskosten": {
"account_number": "4998",
"account_type": "Expenses Included In Valuation"
},
"Gegenkonto 4996-4998": {
"account_number": "4999"
},
"Abschreibungen": {
"is_group": 1,
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
"account_number": "4830",
"account_type": "Accumulated Depreciation"
},
"Abschreibungen auf Gebäude": {
"account_number": "4831",
"account_type": "Depreciation"
},
"Abschreibungen auf Kfz": {
"account_number": "4832",
"account_type": "Depreciation"
},
"Sofortabschreibung GWG": {
"account_number": "4855",
"account_type": "Expense Account"
}
},
"Kfz-Kosten": {
"is_group": 1,
"Kfz-Steuer": {
"account_number": "4510",
"account_type": "Expense Account"
},
"Kfz-Versicherungen": {
"account_number": "4520",
"account_type": "Expense Account"
},
"laufende Kfz-Betriebskosten": {
"account_number": "4530",
"account_type": "Expense Account"
},
"Kfz-Reparaturen": {
"account_number": "4540",
"account_type": "Expense Account"
},
"Fremdfahrzeuge": {
"account_number": "4570",
"account_type": "Expense Account"
},
"sonstige Kfz-Kosten": {
"account_number": "4580",
"account_type": "Expense Account"
}
},
"Personalkosten": {
"is_group": 1,
"Geh\u00e4lter": {
"account_number": "4120",
"account_type": "Expense Account"
},
"gesetzliche soziale Aufwendungen": {
"account_number": "4130",
"account_type": "Expense Account"
},
"Aufwendungen f\u00fcr Altersvorsorge": {
"account_number": "4165",
"account_type": "Expense Account"
},
"Verm\u00f6genswirksame Leistungen": {
"account_number": "4170",
"account_type": "Expense Account"
},
"Aushilfsl\u00f6hne": {
"account_number": "4190",
"account_type": "Expense Account"
}
},
"Raumkosten": {
"is_group": 1,
"Miete und Nebenkosten": {
"account_number": "4210",
"account_type": "Expense Account"
},
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
"account_number": "4240",
"account_type": "Expense Account"
},
"Reinigung": {
"account_number": "4250",
"account_type": "Expense Account"
}
},
"Reparatur/Instandhaltung": {
"is_group": 1,
"Reparatur u. Instandh. von Anlagen/Maschinen u. Betriebs- u. Gesch\u00e4ftsausst.": {
"account_number": "4805",
"account_type": "Expense Account"
}
},
"Versicherungsbeitr\u00e4ge": {
"is_group": 1,
"Versicherungen": {
"account_number": "4360",
"account_type": "Expense Account"
},
"Beitr\u00e4ge": {
"account_number": "4380",
"account_type": "Expense Account"
},
"sonstige Ausgaben": {
"account_number": "4390",
"account_type": "Expense Account"
},
"steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
"account_number": "4396",
"account_type": "Expense Account"
}
},
"Werbe-/Reisekosten": {
"is_group": 1,
"Werbekosten": {
"account_number": "4610",
"account_type": "Expense Account"
},
"Aufmerksamkeiten": {
"account_number": "4653",
"account_type": "Expense Account"
},
"nicht abzugsf\u00e4hige Betriebsausg. aus Werbe-, Repr\u00e4s.- u. Reisekosten": {
"account_number": "4665",
"account_type": "Expense Account"
},
"Reisekosten Unternehmer": {
"account_number": "4670",
"account_type": "Expense Account"
}
},
"verschiedene Kosten": {
"is_group": 1,
"Porto": {
"account_number": "4910",
"account_type": "Expense Account"
},
"Telekom": {
"account_number": "4920",
"account_type": "Expense Account"
},
"Mobilfunk D2": {
"account_number": "4921",
"account_type": "Expense Account"
},
"Internet": {
"account_number": "4922",
"account_type": "Expense Account"
},
"B\u00fcrobedarf": {
"account_number": "4930",
"account_type": "Expense Account"
},
"Zeitschriften, B\u00fccher": {
"account_number": "4940",
"account_type": "Expense Account"
},
"Fortbildungskosten": {
"account_number": "4945",
"account_type": "Expense Account"
},
"Buchf\u00fchrungskosten": {
"account_number": "4955",
"account_type": "Expense Account"
},
"Abschlu\u00df- u. Pr\u00fcfungskosten": {
"account_number": "4957",
"account_type": "Expense Account"
},
"Nebenkosten des Geldverkehrs": {
"account_number": "4970",
"account_type": "Expense Account"
},
"Werkzeuge und Kleinger\u00e4te": {
"account_number": "4985",
"account_type": "Expense Account"
}
},
"Zinsaufwendungen": {
"is_group": 1,
"Zinsaufwendungen f\u00fcr kurzfristige Verbindlichkeiten": {
"account_number": "2110",
"account_type": "Expense Account"
},
"Zinsaufwendungen f\u00fcr KFZ Finanzierung": {
"account_number": "2121",
"account_type": "Expense Account"
}
}
},
"Anfangsbestand 9": {
"is_group": 1,
"root_type": "Equity",
"Saldenvortragskonten": {
"is_group": 1,
"Saldenvortrag Sachkonten": {
"account_number": "9000"
},
"Saldenvortr\u00e4ge Debitoren": {
"account_number": "9008"
},
"Saldenvortr\u00e4ge Kreditoren": {
"account_number": "9009"
}
}
},
"Privatkonten 1": {
"is_group": 1,
"root_type": "Equity",
"Privatentnahmen/-einlagen": {
"is_group": 1,
"Privatentnahme allgemein": {
"account_number": "1800"
},
"Privatsteuern": {
"account_number": "1810"
},
"Sonderausgaben beschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1820"
},
"Sonderausgaben unbeschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1830"
},
"Au\u00dfergew\u00f6hnliche Belastungen": {
"account_number": "1850"
},
"Privateinlagen": {
"account_number": "1890"
}
}
}
}
}

View File

@@ -292,21 +292,18 @@
"Umsatzsteuerforderungen fr\u00fchere Jahre": {} "Umsatzsteuerforderungen fr\u00fchere Jahre": {}
}, },
"Sonstige Verm\u00f6gensgegenst\u00e4nde oder sonstige Verbindlichkeiten": { "Sonstige Verm\u00f6gensgegenst\u00e4nde oder sonstige Verbindlichkeiten": {
"Abziehbare Vorsteuer": { "Abziehbare Vorsteuer": {},
"account_type": "Tax", "Abziehbare Vorsteuer 16%": {},
"is_group": 1, "Abziehbare Vorsteuer 19%": {},
"Abziehbare Vorsteuer 16%": {}, "Abziehbare Vorsteuer 7%": {},
"Abziehbare Vorsteuer 19%": {}, "Abziehbare Vorsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Unsatzsteuerlager": {},
"Abziehbare Vorsteuer 7%": {}, "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb": {},
"Abziehbare Vorsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Unsatzsteuerlager": {}, "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 16%": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb": {}, "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19%": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 16%": {}, "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19%": {}, "Abziehbare Vorsteuer nach \u00a7 13b UStG ": {},
"Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {}, "Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG ": {}, "Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {},
"Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {}
},
"Aufl\u00f6sung Vorsteuer aus Vorjahr \u00a7 4/3 EStG": {}, "Aufl\u00f6sung Vorsteuer aus Vorjahr \u00a7 4/3 EStG": {},
"Aufzuteilende Vorsteuer": {}, "Aufzuteilende Vorsteuer": {},
"Aufzuteilende Vorsteuer 16%": {}, "Aufzuteilende Vorsteuer 16%": {},
@@ -676,26 +673,23 @@
"Sonstige Verrechnungskonten (Interimskonto)": { "Sonstige Verrechnungskonten (Interimskonto)": {
"account_type": "Stock Received But Not Billed" "account_type": "Stock Received But Not Billed"
}, },
"Umsatzsteuer": { "Umsatzsteuer": {},
"account_type": "Tax", "Umsatzsteuer 16%": {},
"is_group": 1, "Umsatzsteuer 19%": {},
"Umsatzsteuer 16%": {}, "Umsatzsteuer 7%": {},
"Umsatzsteuer 19%": {}, "Umsatzsteuer Vorjahr": {},
"Umsatzsteuer 7%": {}, "Umsatzsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Umsatzsteuerlager": {},
"Umsatzsteuer Vorjahr": {}, "Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen": {},
"Umsatzsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Umsatzsteuerlager": {}, "Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen 19%": {},
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen": {}, "Umsatzsteuer aus innergemeinschaftlichem Erwerb ": {},
"Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen 19%": {}, "Umsatzsteuer aus innergemeinschaftlichem Erwerb 16%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ": {}, "Umsatzsteuer aus innergemeinschaftlichem Erwerb 19%": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 16%": {}, "Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb 19%": {}, "Umsatzsteuer fr\u00fchere Jahre": {},
"Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug": {}, "Umsatzsteuer laufendes Jahr": {},
"Umsatzsteuer fr\u00fchere Jahre": {}, "Umsatzsteuer nach \u00a713b UStG": {},
"Umsatzsteuer laufendes Jahr": {}, "Umsatzsteuer nach \u00a713b UStG 16%": {},
"Umsatzsteuer nach \u00a713b UStG": {}, "Umsatzsteuer nach \u00a713b UStG 19%": {},
"Umsatzsteuer nach \u00a713b UStG 16%": {},
"Umsatzsteuer nach \u00a713b UStG 19%": {}
},
"Umsatzsteuer- Vorauszahlungen": {}, "Umsatzsteuer- Vorauszahlungen": {},
"Umsatzsteuer- Vorauszahlungen 1/11": {}, "Umsatzsteuer- Vorauszahlungen 1/11": {},
"Verbindlichkeiten aus Lohn- und Kirchensteuer": {} "Verbindlichkeiten aus Lohn- und Kirchensteuer": {}

View File

@@ -1,109 +1,187 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _ from frappe import _
def get(): def get():
return { return {
_("Application of Funds (Assets)"): { _("Application of Funds (Assets)"): {
_("Current Assets"): { _("Current Assets"): {
_("Accounts Receivable"): {_("Debtors"): {"account_type": "Receivable"}}, _("Accounts Receivable"): {
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1}, _("Debtors"): {
_("Cash In Hand"): {_("Cash"): {"account_type": "Cash"}, "account_type": "Cash"}, "account_type": "Receivable"
_("Loans and Advances (Assets)"): { }
_("Employee Advances"): {}, },
_("Bank Accounts"): {
"account_type": "Bank",
"is_group": 1
},
_("Cash In Hand"): {
_("Cash"): {
"account_type": "Cash"
},
"account_type": "Cash"
},
_("Loans and Advances (Assets)"): {
_("Employee Advances"): {
},
},
_("Securities and Deposits"): {
_("Earnest Money"): {}
},
_("Stock Assets"): {
_("Stock In Hand"): {
"account_type": "Stock"
},
"account_type": "Stock",
},
_("Tax Assets"): {
"is_group": 1
}
},
_("Fixed Assets"): {
_("Capital Equipments"): {
"account_type": "Fixed Asset"
},
_("Electronic Equipments"): {
"account_type": "Fixed Asset"
},
_("Furnitures and Fixtures"): {
"account_type": "Fixed Asset"
},
_("Office Equipments"): {
"account_type": "Fixed Asset"
},
_("Plants and Machineries"): {
"account_type": "Fixed Asset"
},
_("Buildings"): {
"account_type": "Fixed Asset"
}, },
_("Securities and Deposits"): {_("Earnest Money"): {}}, _("Softwares"): {
_("Stock Assets"): { "account_type": "Fixed Asset"
_("Stock In Hand"): {"account_type": "Stock"},
"account_type": "Stock",
}, },
_("Tax Assets"): {"is_group": 1}, _("Accumulated Depreciation"): {
}, "account_type": "Accumulated Depreciation"
_("Fixed Assets"): { },
_("Capital Equipments"): {"account_type": "Fixed Asset"}, _("CWIP Account"): {
_("Electronic Equipments"): {"account_type": "Fixed Asset"}, "account_type": "Capital Work in Progress",
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset"}, }
_("Office Equipments"): {"account_type": "Fixed Asset"}, },
_("Plants and Machineries"): {"account_type": "Fixed Asset"}, _("Investments"): {
_("Buildings"): {"account_type": "Fixed Asset"}, "is_group": 1
_("Softwares"): {"account_type": "Fixed Asset"}, },
_("Accumulated Depreciation"): {"account_type": "Accumulated Depreciation"}, _("Temporary Accounts"): {
_("CWIP Account"): { _("Temporary Opening"): {
"account_type": "Capital Work in Progress", "account_type": "Temporary"
}, }
}, },
_("Investments"): {"is_group": 1}, "root_type": "Asset"
_("Temporary Accounts"): {_("Temporary Opening"): {"account_type": "Temporary"}}, },
"root_type": "Asset", _("Expenses"): {
}, _("Direct Expenses"): {
_("Expenses"): { _("Stock Expenses"): {
_("Direct Expenses"): { _("Cost of Goods Sold"): {
_("Stock Expenses"): { "account_type": "Cost of Goods Sold"
_("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold"}, },
_("Expenses Included In Asset Valuation"): { _("Expenses Included In Asset Valuation"): {
"account_type": "Expenses Included In Asset Valuation" "account_type": "Expenses Included In Asset Valuation"
}, },
_("Expenses Included In Valuation"): {"account_type": "Expenses Included In Valuation"}, _("Expenses Included In Valuation"): {
_("Stock Adjustment"): {"account_type": "Stock Adjustment"}, "account_type": "Expenses Included In Valuation"
}, },
}, _("Stock Adjustment"): {
_("Indirect Expenses"): { "account_type": "Stock Adjustment"
_("Administrative Expenses"): {}, }
_("Commission on Sales"): {}, },
_("Depreciation"): {"account_type": "Depreciation"}, },
_("Entertainment Expenses"): {}, _("Indirect Expenses"): {
_("Freight and Forwarding Charges"): {"account_type": "Chargeable"}, _("Administrative Expenses"): {},
_("Legal Expenses"): {}, _("Commission on Sales"): {},
_("Marketing Expenses"): {"account_type": "Chargeable"}, _("Depreciation"): {
_("Miscellaneous Expenses"): {"account_type": "Chargeable"}, "account_type": "Depreciation"
_("Office Maintenance Expenses"): {}, },
_("Office Rent"): {}, _("Entertainment Expenses"): {},
_("Postal Expenses"): {}, _("Freight and Forwarding Charges"): {
_("Print and Stationery"): {}, "account_type": "Chargeable"
_("Round Off"): {"account_type": "Round Off"}, },
_("Salary"): {}, _("Legal Expenses"): {},
_("Sales Expenses"): {}, _("Marketing Expenses"): {
_("Telephone Expenses"): {}, "account_type": "Chargeable"
_("Travel Expenses"): {}, },
_("Utility Expenses"): {}, _("Miscellaneous Expenses"): {
"account_type": "Chargeable"
},
_("Office Maintenance Expenses"): {},
_("Office Rent"): {},
_("Postal Expenses"): {},
_("Print and Stationery"): {},
_("Round Off"): {
"account_type": "Round Off"
},
_("Salary"): {},
_("Sales Expenses"): {},
_("Telephone Expenses"): {},
_("Travel Expenses"): {},
_("Utility Expenses"): {},
_("Write Off"): {}, _("Write Off"): {},
_("Exchange Gain/Loss"): {}, _("Exchange Gain/Loss"): {},
_("Gain/Loss on Asset Disposal"): {}, _("Gain/Loss on Asset Disposal"): {}
}, },
"root_type": "Expense", "root_type": "Expense"
}, },
_("Income"): { _("Income"): {
_("Direct Income"): {_("Sales"): {}, _("Service"): {}}, _("Direct Income"): {
_("Indirect Income"): {"is_group": 1}, _("Sales"): {},
"root_type": "Income", _("Service"): {}
}, },
_("Source of Funds (Liabilities)"): { _("Indirect Income"): {
_("Current Liabilities"): { "is_group": 1
_("Accounts Payable"): { },
_("Creditors"): {"account_type": "Payable"}, "root_type": "Income"
_("Payroll Payable"): {}, },
_("Source of Funds (Liabilities)"): {
_("Current Liabilities"): {
_("Accounts Payable"): {
_("Creditors"): {
"account_type": "Payable"
},
_("Payroll Payable"): {},
},
_("Stock Liabilities"): {
_("Stock Received But Not Billed"): {
"account_type": "Stock Received But Not Billed"
},
_("Asset Received But Not Billed"): {
"account_type": "Asset Received But Not Billed"
}
},
_("Duties and Taxes"): {
"account_type": "Tax",
"is_group": 1
}, },
_("Stock Liabilities"): {
_("Stock Received But Not Billed"): {"account_type": "Stock Received But Not Billed"},
_("Asset Received But Not Billed"): {"account_type": "Asset Received But Not Billed"},
},
_("Duties and Taxes"): {"account_type": "Tax", "is_group": 1},
_("Loans (Liabilities)"): { _("Loans (Liabilities)"): {
_("Secured Loans"): {}, _("Secured Loans"): {},
_("Unsecured Loans"): {}, _("Unsecured Loans"): {},
_("Bank Overdraft Account"): {}, _("Bank Overdraft Account"): {},
}, },
}, },
"root_type": "Liability", "root_type": "Liability"
}, },
_("Equity"): { _("Equity"): {
_("Capital Stock"): {"account_type": "Equity"}, _("Capital Stock"): {
_("Dividends Paid"): {"account_type": "Equity"}, "account_type": "Equity"
_("Opening Balance Equity"): {"account_type": "Equity"}, },
_("Retained Earnings"): {"account_type": "Equity"}, _("Dividends Paid"): {
"root_type": "Equity", "account_type": "Equity"
}, },
_("Opening Balance Equity"): {
"account_type": "Equity"
},
_("Retained Earnings"): {
"account_type": "Equity"
},
"root_type": "Equity"
}
} }

View File

@@ -1,158 +1,289 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _ from frappe import _
def get(): def get():
return { return {
_("Application of Funds (Assets)"): { _("Application of Funds (Assets)"): {
_("Current Assets"): { _("Current Assets"): {
_("Accounts Receivable"): { _("Accounts Receivable"): {
_("Debtors"): {"account_type": "Receivable", "account_number": "1310"}, _("Debtors"): {
"account_number": "1300", "account_type": "Receivable",
}, "account_number": "1310"
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1, "account_number": "1200"}, },
_("Cash In Hand"): { "account_number": "1300"
_("Cash"): {"account_type": "Cash", "account_number": "1110"}, },
"account_type": "Cash", _("Bank Accounts"): {
"account_number": "1100", "account_type": "Bank",
}, "is_group": 1,
_("Loans and Advances (Assets)"): { "account_number": "1200"
_("Employee Advances"): {"account_number": "1610"}, },
"account_number": "1600", _("Cash In Hand"): {
}, _("Cash"): {
_("Securities and Deposits"): { "account_type": "Cash",
_("Earnest Money"): {"account_number": "1651"}, "account_number": "1110"
"account_number": "1650", },
}, "account_type": "Cash",
_("Stock Assets"): { "account_number": "1100"
_("Stock In Hand"): {"account_type": "Stock", "account_number": "1410"}, },
"account_type": "Stock", _("Loans and Advances (Assets)"): {
"account_number": "1400", _("Employee Advances"): {
}, "account_number": "1610"
_("Tax Assets"): {"is_group": 1, "account_number": "1500"}, },
"account_number": "1100-1600", "account_number": "1600"
}, },
_("Fixed Assets"): { _("Securities and Deposits"): {
_("Capital Equipments"): {"account_type": "Fixed Asset", "account_number": "1710"}, _("Earnest Money"): {
_("Electronic Equipments"): {"account_type": "Fixed Asset", "account_number": "1720"}, "account_number": "1651"
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset", "account_number": "1730"}, },
_("Office Equipments"): {"account_type": "Fixed Asset", "account_number": "1740"}, "account_number": "1650"
_("Plants and Machineries"): {"account_type": "Fixed Asset", "account_number": "1750"}, },
_("Buildings"): {"account_type": "Fixed Asset", "account_number": "1760"}, _("Stock Assets"): {
_("Softwares"): {"account_type": "Fixed Asset", "account_number": "1770"}, _("Stock In Hand"): {
_("Accumulated Depreciation"): { "account_type": "Stock",
"account_type": "Accumulated Depreciation", "account_number": "1410"
"account_number": "1780", },
}, "account_type": "Stock",
_("CWIP Account"): {"account_type": "Capital Work in Progress", "account_number": "1790"}, "account_number": "1400"
"account_number": "1700", },
}, _("Tax Assets"): {
_("Investments"): {"is_group": 1, "account_number": "1800"}, "is_group": 1,
_("Temporary Accounts"): { "account_number": "1500"
_("Temporary Opening"): {"account_type": "Temporary", "account_number": "1910"}, },
"account_number": "1900", "account_number": "1100-1600"
}, },
"root_type": "Asset", _("Fixed Assets"): {
"account_number": "1000", _("Capital Equipments"): {
}, "account_type": "Fixed Asset",
_("Expenses"): { "account_number": "1710"
_("Direct Expenses"): { },
_("Stock Expenses"): { _("Electronic Equipments"): {
_("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold", "account_number": "5111"}, "account_type": "Fixed Asset",
_("Expenses Included In Asset Valuation"): { "account_number": "1720"
"account_type": "Expenses Included In Asset Valuation", },
"account_number": "5112", _("Furnitures and Fixtures"): {
}, "account_type": "Fixed Asset",
_("Expenses Included In Valuation"): { "account_number": "1730"
"account_type": "Expenses Included In Valuation", },
"account_number": "5118", _("Office Equipments"): {
}, "account_type": "Fixed Asset",
_("Stock Adjustment"): {"account_type": "Stock Adjustment", "account_number": "5119"}, "account_number": "1740"
"account_number": "5110", },
}, _("Plants and Machineries"): {
"account_number": "5100", "account_type": "Fixed Asset",
}, "account_number": "1750"
_("Indirect Expenses"): { },
_("Administrative Expenses"): {"account_number": "5201"}, _("Buildings"): {
_("Commission on Sales"): {"account_number": "5202"}, "account_type": "Fixed Asset",
_("Depreciation"): {"account_type": "Depreciation", "account_number": "5203"}, "account_number": "1760"
_("Entertainment Expenses"): {"account_number": "5204"}, },
_("Freight and Forwarding Charges"): {"account_type": "Chargeable", "account_number": "5205"}, _("Softwares"): {
_("Legal Expenses"): {"account_number": "5206"}, "account_type": "Fixed Asset",
_("Marketing Expenses"): {"account_type": "Chargeable", "account_number": "5207"}, "account_number": "1770"
_("Office Maintenance Expenses"): {"account_number": "5208"}, },
_("Office Rent"): {"account_number": "5209"}, _("Accumulated Depreciation"): {
_("Postal Expenses"): {"account_number": "5210"}, "account_type": "Accumulated Depreciation",
_("Print and Stationery"): {"account_number": "5211"}, "account_number": "1780"
_("Round Off"): {"account_type": "Round Off", "account_number": "5212"}, },
_("Salary"): {"account_number": "5213"}, _("CWIP Account"): {
_("Sales Expenses"): {"account_number": "5214"}, "account_type": "Capital Work in Progress",
_("Telephone Expenses"): {"account_number": "5215"}, "account_number": "1790"
_("Travel Expenses"): {"account_number": "5216"}, },
_("Utility Expenses"): {"account_number": "5217"}, "account_number": "1700"
_("Write Off"): {"account_number": "5218"}, },
_("Exchange Gain/Loss"): {"account_number": "5219"}, _("Investments"): {
_("Gain/Loss on Asset Disposal"): {"account_number": "5220"}, "is_group": 1,
_("Miscellaneous Expenses"): {"account_type": "Chargeable", "account_number": "5221"}, "account_number": "1800"
"account_number": "5200", },
}, _("Temporary Accounts"): {
"root_type": "Expense", _("Temporary Opening"): {
"account_number": "5000", "account_type": "Temporary",
}, "account_number": "1910"
_("Income"): { },
_("Direct Income"): { "account_number": "1900"
_("Sales"): {"account_number": "4110"}, },
_("Service"): {"account_number": "4120"}, "root_type": "Asset",
"account_number": "4100", "account_number": "1000"
}, },
_("Indirect Income"): {"is_group": 1, "account_number": "4200"}, _("Expenses"): {
"root_type": "Income", _("Direct Expenses"): {
"account_number": "4000", _("Stock Expenses"): {
}, _("Cost of Goods Sold"): {
_("Source of Funds (Liabilities)"): { "account_type": "Cost of Goods Sold",
_("Current Liabilities"): { "account_number": "5111"
_("Accounts Payable"): { },
_("Creditors"): {"account_type": "Payable", "account_number": "2110"}, _("Expenses Included In Asset Valuation"): {
_("Payroll Payable"): {"account_number": "2120"}, "account_type": "Expenses Included In Asset Valuation",
"account_number": "2100", "account_number": "5112"
}, },
_("Stock Liabilities"): { _("Expenses Included In Valuation"): {
_("Stock Received But Not Billed"): { "account_type": "Expenses Included In Valuation",
"account_type": "Stock Received But Not Billed", "account_number": "5118"
"account_number": "2210", },
}, _("Stock Adjustment"): {
_("Asset Received But Not Billed"): { "account_type": "Stock Adjustment",
"account_type": "Asset Received But Not Billed", "account_number": "5119"
"account_number": "2211", },
}, "account_number": "5110"
"account_number": "2200", },
}, "account_number": "5100"
_("Duties and Taxes"): { },
_("TDS Payable"): {"account_number": "2310"}, _("Indirect Expenses"): {
"account_type": "Tax", _("Administrative Expenses"): {
"is_group": 1, "account_number": "5201"
"account_number": "2300", },
}, _("Commission on Sales"): {
_("Loans (Liabilities)"): { "account_number": "5202"
_("Secured Loans"): {"account_number": "2410"}, },
_("Unsecured Loans"): {"account_number": "2420"}, _("Depreciation"): {
_("Bank Overdraft Account"): {"account_number": "2430"}, "account_type": "Depreciation",
"account_number": "2400", "account_number": "5203"
}, },
"account_number": "2100-2400", _("Entertainment Expenses"): {
}, "account_number": "5204"
"root_type": "Liability", },
"account_number": "2000", _("Freight and Forwarding Charges"): {
}, "account_type": "Chargeable",
_("Equity"): { "account_number": "5205"
_("Capital Stock"): {"account_type": "Equity", "account_number": "3100"}, },
_("Dividends Paid"): {"account_type": "Equity", "account_number": "3200"}, _("Legal Expenses"): {
_("Opening Balance Equity"): {"account_type": "Equity", "account_number": "3300"}, "account_number": "5206"
_("Retained Earnings"): {"account_type": "Equity", "account_number": "3400"}, },
"root_type": "Equity", _("Marketing Expenses"): {
"account_number": "3000", "account_type": "Chargeable",
}, "account_number": "5207"
} },
_("Office Maintenance Expenses"): {
"account_number": "5208"
},
_("Office Rent"): {
"account_number": "5209"
},
_("Postal Expenses"): {
"account_number": "5210"
},
_("Print and Stationery"): {
"account_number": "5211"
},
_("Round Off"): {
"account_type": "Round Off",
"account_number": "5212"
},
_("Salary"): {
"account_number": "5213"
},
_("Sales Expenses"): {
"account_number": "5214"
},
_("Telephone Expenses"): {
"account_number": "5215"
},
_("Travel Expenses"): {
"account_number": "5216"
},
_("Utility Expenses"): {
"account_number": "5217"
},
_("Write Off"): {
"account_number": "5218"
},
_("Exchange Gain/Loss"): {
"account_number": "5219"
},
_("Gain/Loss on Asset Disposal"): {
"account_number": "5220"
},
_("Miscellaneous Expenses"): {
"account_type": "Chargeable",
"account_number": "5221"
},
"account_number": "5200"
},
"root_type": "Expense",
"account_number": "5000"
},
_("Income"): {
_("Direct Income"): {
_("Sales"): {
"account_number": "4110"
},
_("Service"): {
"account_number": "4120"
},
"account_number": "4100"
},
_("Indirect Income"): {
"is_group": 1,
"account_number": "4200"
},
"root_type": "Income",
"account_number": "4000"
},
_("Source of Funds (Liabilities)"): {
_("Current Liabilities"): {
_("Accounts Payable"): {
_("Creditors"): {
"account_type": "Payable",
"account_number": "2110"
},
_("Payroll Payable"): {
"account_number": "2120"
},
"account_number": "2100"
},
_("Stock Liabilities"): {
_("Stock Received But Not Billed"): {
"account_type": "Stock Received But Not Billed",
"account_number": "2210"
},
_("Asset Received But Not Billed"): {
"account_type": "Asset Received But Not Billed",
"account_number": "2211"
},
"account_number": "2200"
},
_("Duties and Taxes"): {
"account_type": "Tax",
"is_group": 1,
"account_number": "2300"
},
_("Loans (Liabilities)"): {
_("Secured Loans"): {
"account_number": "2410"
},
_("Unsecured Loans"): {
"account_number": "2420"
},
_("Bank Overdraft Account"): {
"account_number": "2430"
},
"account_number": "2400"
},
"account_number": "2100-2400"
},
"root_type": "Liability",
"account_number": "2000"
},
_("Equity"): {
_("Capital Stock"): {
"account_type": "Equity",
"account_number": "3100"
},
_("Dividends Paid"): {
"account_type": "Equity",
"account_number": "3200"
},
_("Opening Balance Equity"): {
"account_type": "Equity",
"account_number": "3300"
},
_("Retained Earnings"): {
"account_type": "Equity",
"account_number": "3400"
},
"root_type": "Equity",
"account_number": "3000"
}
}

View File

@@ -1,14 +1,12 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
from erpnext.stock import get_warehouse_account, get_company_default_inventory_account
from erpnext.accounts.doctype.account.account import merge_account, update_account_number from erpnext.accounts.doctype.account.account import update_account_number
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account from erpnext.accounts.doctype.account.account import merge_account
class TestAccount(unittest.TestCase): class TestAccount(unittest.TestCase):
def test_rename_account(self): def test_rename_account(self):
@@ -20,9 +18,8 @@ class TestAccount(unittest.TestCase):
acc.company = "_Test Company" acc.company = "_Test Company"
acc.insert() acc.insert()
account_number, account_name = frappe.db.get_value( account_number, account_name = frappe.db.get_value("Account", "1210 - Debtors - _TC",
"Account", "1210 - Debtors - _TC", ["account_number", "account_name"] ["account_number", "account_name"])
)
self.assertEqual(account_number, "1210") self.assertEqual(account_number, "1210")
self.assertEqual(account_name, "Debtors") self.assertEqual(account_name, "Debtors")
@@ -31,12 +28,8 @@ class TestAccount(unittest.TestCase):
update_account_number("1210 - Debtors - _TC", new_account_name, new_account_number) update_account_number("1210 - Debtors - _TC", new_account_name, new_account_number)
new_acc = frappe.db.get_value( new_acc = frappe.db.get_value("Account", "1211-11-4 - 6 - - Debtors 1 - Test - - _TC",
"Account", ["account_name", "account_number"], as_dict=1)
"1211-11-4 - 6 - - Debtors 1 - Test - - _TC",
["account_name", "account_number"],
as_dict=1,
)
self.assertEqual(new_acc.account_name, "Debtors 1 - Test -") self.assertEqual(new_acc.account_name, "Debtors 1 - Test -")
self.assertEqual(new_acc.account_number, "1211-11-4 - 6 -") self.assertEqual(new_acc.account_number, "1211-11-4 - 6 -")
@@ -76,7 +69,6 @@ class TestAccount(unittest.TestCase):
acc.account_name = "Accumulated Depreciation" acc.account_name = "Accumulated Depreciation"
acc.parent_account = "Fixed Assets - _TC" acc.parent_account = "Fixed Assets - _TC"
acc.company = "_Test Company" acc.company = "_Test Company"
acc.account_type = "Accumulated Depreciation"
acc.insert() acc.insert()
doc = frappe.get_doc("Account", "Securities and Deposits - _TC") doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
@@ -84,9 +76,7 @@ class TestAccount(unittest.TestCase):
self.assertEqual(parent, "Securities and Deposits - _TC") self.assertEqual(parent, "Securities and Deposits - _TC")
merge_account( merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company)
"Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company
)
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account") parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
# Parent account of the child account changes after merging # Parent account of the child account changes after merging
@@ -98,173 +88,29 @@ class TestAccount(unittest.TestCase):
doc = frappe.get_doc("Account", "Current Assets - _TC") doc = frappe.get_doc("Account", "Current Assets - _TC")
# Raise error as is_group property doesn't match # Raise error as is_group property doesn't match
self.assertRaises( self.assertRaises(frappe.ValidationError, merge_account, "Current Assets - _TC",\
frappe.ValidationError, "Accumulated Depreciation - _TC", doc.is_group, doc.root_type, doc.company)
merge_account,
"Current Assets - _TC",
"Accumulated Depreciation - _TC",
doc.is_group,
doc.root_type,
doc.company,
)
doc = frappe.get_doc("Account", "Capital Stock - _TC") doc = frappe.get_doc("Account", "Capital Stock - _TC")
# Raise error as root_type property doesn't match # Raise error as root_type property doesn't match
self.assertRaises( self.assertRaises(frappe.ValidationError, merge_account, "Capital Stock - _TC",\
frappe.ValidationError, "Softwares - _TC", doc.is_group, doc.root_type, doc.company)
merge_account,
"Capital Stock - _TC",
"Softwares - _TC",
doc.is_group,
doc.root_type,
doc.company,
)
def test_account_sync(self): def test_account_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None) del frappe.local.flags["ignore_root_company_validation"]
acc = frappe.new_doc("Account") acc = frappe.new_doc("Account")
acc.account_name = "Test Sync Account" acc.account_name = "Test Sync Account"
acc.parent_account = "Temporary Accounts - _TC3" acc.parent_account = "Temporary Accounts - _TC3"
acc.company = "_Test Company 3" acc.company = "_Test Company 3"
acc.insert() acc.insert()
acc_tc_4 = frappe.db.get_value( acc_tc_4 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 4"})
"Account", {"account_name": "Test Sync Account", "company": "_Test Company 4"} acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 5"})
)
acc_tc_5 = frappe.db.get_value(
"Account", {"account_name": "Test Sync Account", "company": "_Test Company 5"}
)
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4") self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5") self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
def test_add_account_to_a_group(self): def _make_test_records(verbose):
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 1)
acc = frappe.new_doc("Account")
acc.account_name = "Test Group Account"
acc.parent_account = "Office Rent - _TC3"
acc.company = "_Test Company 3"
self.assertRaises(frappe.ValidationError, acc.insert)
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 0)
def test_account_rename_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None)
acc = frappe.new_doc("Account")
acc.account_name = "Test Rename Account"
acc.parent_account = "Temporary Accounts - _TC3"
acc.company = "_Test Company 3"
acc.insert()
# Rename account in parent company
update_account_number(acc.name, "Test Rename Sync Account", "1234")
# Check if renamed in children
self.assertTrue(
frappe.db.exists(
"Account",
{
"account_name": "Test Rename Sync Account",
"company": "_Test Company 4",
"account_number": "1234",
},
)
)
self.assertTrue(
frappe.db.exists(
"Account",
{
"account_name": "Test Rename Sync Account",
"company": "_Test Company 5",
"account_number": "1234",
},
)
)
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC3")
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC5")
def test_child_company_account_rename_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None)
acc = frappe.new_doc("Account")
acc.account_name = "Test Group Account"
acc.parent_account = "Temporary Accounts - _TC3"
acc.is_group = 1
acc.company = "_Test Company 3"
acc.insert()
self.assertTrue(
frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 4"}
)
)
self.assertTrue(
frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
)
# Try renaming child company account
acc_tc_5 = frappe.db.get_value(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
self.assertRaises(
frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account"
)
# Rename child company account with allow_account_creation_against_child_company enabled
frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 1
)
update_account_number(acc_tc_5, "Test Modified Account")
self.assertTrue(
frappe.db.exists(
"Account", {"name": "Test Modified Account - _TC5", "company": "_Test Company 5"}
)
)
frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 0
)
to_delete = [
"Test Group Account - _TC3",
"Test Group Account - _TC4",
"Test Modified Account - _TC5",
]
for doc in to_delete:
frappe.delete_doc("Account", doc)
def test_validate_account_currency(self):
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
if not frappe.db.get_value("Account", "Test Currency Account - _TC"):
acc = frappe.new_doc("Account")
acc.account_name = "Test Currency Account"
acc.parent_account = "Tax Assets - _TC"
acc.company = "_Test Company"
acc.insert()
else:
acc = frappe.get_doc("Account", "Test Currency Account - _TC")
self.assertEqual(acc.account_currency, "INR")
# Make a JV against this account
make_journal_entry(
"Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True
)
acc.account_currency = "USD"
self.assertRaises(frappe.ValidationError, acc.save)
def _make_test_records(verbose=None):
from frappe.test_runner import make_test_objects from frappe.test_runner import make_test_objects
accounts = [ accounts = [
@@ -273,16 +119,20 @@ def _make_test_records(verbose=None):
["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"], ["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"],
["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"], ["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"],
["_Test Cash", "Cash In Hand", 0, "Cash", None], ["_Test Cash", "Cash In Hand", 0, "Cash", None],
["_Test Account Stock Expenses", "Direct Expenses", 1, None, None], ["_Test Account Stock Expenses", "Direct Expenses", 1, None, None],
["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable", None], ["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
["_Test Account Customs Duty", "_Test Account Stock Expenses", 0, "Tax", None], ["_Test Account Customs Duty", "_Test Account Stock Expenses", 0, "Tax", None],
["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable", None], ["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
["_Test Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment", None], ["_Test Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment", None],
["_Test Employee Advance", "Current Liabilities", 0, None, None], ["_Test Employee Advance", "Current Liabilities", 0, None, None],
["_Test Account Tax Assets", "Current Assets", 1, None, None], ["_Test Account Tax Assets", "Current Assets", 1, None, None],
["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None], ["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Service Tax", "_Test Account Tax Assets", 0, "Tax", None], ["_Test Account Service Tax", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Reserves and Surplus", "Current Liabilities", 0, None, None], ["_Test Account Reserves and Surplus", "Current Liabilities", 0, None, None],
["_Test Account Cost for Goods Sold", "Expenses", 0, None, None], ["_Test Account Cost for Goods Sold", "Expenses", 0, None, None],
["_Test Account Excise Duty", "_Test Account Tax Assets", 0, "Tax", None], ["_Test Account Excise Duty", "_Test Account Tax Assets", 0, "Tax", None],
["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None], ["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None],
@@ -291,45 +141,38 @@ def _make_test_records(verbose=None):
["_Test Account Discount", "Direct Expenses", 0, None, None], ["_Test Account Discount", "Direct Expenses", 0, None, None],
["_Test Write Off", "Indirect Expenses", 0, None, None], ["_Test Write Off", "Indirect Expenses", 0, None, None],
["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None], ["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None],
["_Test Account Sales", "Direct Income", 0, None, None], ["_Test Account Sales", "Direct Income", 0, None, None],
# related to Account Inventory Integration # related to Account Inventory Integration
["_Test Account Stock In Hand", "Current Assets", 0, None, None], ["_Test Account Stock In Hand", "Current Assets", 0, None, None],
# fixed asset depreciation # fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None], ["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None], ["_Test Accumulated Depreciations", "Current Assets", 0, None, None],
["_Test Depreciations", "Expenses", 0, None, None], ["_Test Depreciations", "Expenses", 0, None, None],
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None], ["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
# Receivable / Payable Account # Receivable / Payable Account
["_Test Receivable", "Current Assets", 0, "Receivable", None], ["_Test Receivable", "Current Assets", 0, "Receivable", None],
["_Test Payable", "Current Liabilities", 0, "Payable", None], ["_Test Payable", "Current Liabilities", 0, "Payable", None],
["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"], ["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"],
["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"], ["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"]
] ]
for company, abbr in [ for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"], ["_Test Company with perpetual inventory", "TCP1"]]:
["_Test Company", "_TC"], test_objects = make_test_objects("Account", [{
["_Test Company 1", "_TC1"], "doctype": "Account",
["_Test Company with perpetual inventory", "TCP1"], "account_name": account_name,
]: "parent_account": parent_account + " - " + abbr,
test_objects = make_test_objects( "company": company,
"Account", "is_group": is_group,
[ "account_type": account_type,
{ "account_currency": currency
"doctype": "Account", } for account_name, parent_account, is_group, account_type, currency in accounts])
"account_name": account_name,
"parent_account": parent_account + " - " + abbr,
"company": company,
"is_group": is_group,
"account_type": account_type,
"account_currency": currency,
}
for account_name, parent_account, is_group, account_type, currency in accounts
],
)
return test_objects return test_objects
def get_inventory_account(company, warehouse=None): def get_inventory_account(company, warehouse=None):
account = None account = None
if warehouse: if warehouse:
@@ -339,24 +182,18 @@ def get_inventory_account(company, warehouse=None):
return account return account
def create_account(**kwargs): def create_account(**kwargs):
account = frappe.db.get_value( account = frappe.db.get_value("Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")})
"Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")}
)
if account: if account:
return account return account
else: else:
account = frappe.get_doc( account = frappe.get_doc(dict(
dict( doctype = "Account",
doctype="Account", account_name = kwargs.get('account_name'),
account_name=kwargs.get("account_name"), account_type = kwargs.get('account_type'),
account_type=kwargs.get("account_type"), parent_account = kwargs.get('parent_account'),
parent_account=kwargs.get("parent_account"), company = kwargs.get('company')
company=kwargs.get("company"), ))
account_currency=kwargs.get("account_currency"),
)
)
account.save() account.save()
return account.name return account.name

View File

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

View File

@@ -0,0 +1,134 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:account_subtype",
"beta": 0,
"creation": "2018-10-25 15:46:08.054586",
"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": "account_subtype",
"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": "Account Subtype",
"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": 1
}
],
"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-10-25 15:47:03.841390",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Subtype",
"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,
"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": "Accounts 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": "Accounts User",
"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": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class AccountSubtype(Document):
pass

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Account Subtype", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Account Subtype
() => frappe.tests.make('Account Subtype', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

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

View File

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

View File

@@ -0,0 +1,134 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:account_type",
"beta": 0,
"creation": "2018-10-25 15:45:45.789963",
"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": "account_type",
"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": "Account Type",
"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": 1
}
],
"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-10-25 15:46:51.042604",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Type",
"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,
"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": "Accounts 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": "Accounts User",
"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": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class AccountType(Document):
pass

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Account Type", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Account Type
() => frappe.tests.make('Account Type', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

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

View File

@@ -2,11 +2,12 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Accounting Dimension', { frappe.ui.form.on('Accounting Dimension', {
refresh: function(frm) { refresh: function(frm) {
frm.set_query('document_type', () => { frm.set_query('document_type', () => {
let invalid_doctypes = frappe.model.core_doctypes_list; let invalid_doctypes = frappe.model.core_doctypes_list;
invalid_doctypes.push('Accounting Dimension', 'Project', invalid_doctypes.push('Accounting Dimension', 'Project',
'Cost Center', 'Accounting Dimension Detail', 'Company'); 'Cost Center', 'Accounting Dimension Detail');
return { return {
filters: { filters: {
@@ -47,6 +48,12 @@ frappe.ui.form.on('Accounting Dimension', {
frm.set_value('label', frm.doc.document_type); frm.set_value('label', frm.doc.document_type);
frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type)); frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type));
if (frm.is_new()){
let row = frappe.model.add_child(frm.doc, "Accounting Dimension Detail", "dimension_defaults");
row.reference_document = frm.doc.document_type;
frm.refresh_fields("dimension_defaults");
}
frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => { frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => {
if (r && r.document_type) { if (r && r.document_type) {
frm.set_df_property('document_type', 'description', "Document type is already set as dimension"); frm.set_df_property('document_type', 'description', "Document type is already set as dimension");

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"autoname": "field:label", "autoname": "field:label",
"creation": "2019-05-04 18:13:37.002352", "creation": "2019-05-04 18:13:37.002352",
"doctype": "DocType", "doctype": "DocType",
@@ -30,7 +29,6 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Reference Document Type", "label": "Reference Document Type",
"options": "DocType", "options": "DocType",
"read_only_depends_on": "eval:!doc.__islocal",
"reqd": 1 "reqd": 1
}, },
{ {
@@ -48,8 +46,7 @@
"options": "Accounting Dimension Detail" "options": "Accounting Dimension Detail"
} }
], ],
"links": [], "modified": "2019-07-17 16:49:31.134385",
"modified": "2021-02-08 16:37:53.936656",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting Dimension", "name": "Accounting Dimension",
@@ -66,20 +63,9 @@
"role": "System Manager", "role": "System Manager",
"share": 1, "share": 1,
"write": 1 "write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
} }
], ],
"quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1 "track_changes": 1

View File

@@ -1,62 +1,45 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import json
import frappe import frappe
from frappe import _, scrub from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field import json
from frappe.model import core_doctypes_list
from frappe.model.document import Document from frappe.model.document import Document
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe import scrub
from frappe.utils import cstr from frappe.utils import cstr
from frappe.utils.background_jobs import enqueue
from frappe.model import core_doctypes_list
class AccountingDimension(Document): class AccountingDimension(Document):
def before_insert(self): def before_insert(self):
self.set_fieldname_and_label() self.set_fieldname_and_label()
def validate(self): def validate(self):
if self.document_type in core_doctypes_list + ( if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
"Accounting Dimension", 'Cost Center', 'Accounting Dimension Detail') :
"Project",
"Cost Center",
"Accounting Dimension Detail",
"Company",
"Account",
):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type) msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg) frappe.throw(msg)
exists = frappe.db.get_value( exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
"Accounting Dimension", {"document_type": self.document_type}, ["name"]
)
if exists and self.is_new(): if exists and self.is_new():
frappe.throw(_("Document Type already used as a dimension")) frappe.throw("Document Type already used as a dimension")
if not self.is_new():
self.validate_document_type_change()
def validate_document_type_change(self):
doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type")
if doctype_before_save != self.document_type:
message = _("Cannot change Reference Document Type.")
message += _("Please create a new Accounting Dimension if required.")
frappe.throw(message)
def after_insert(self): def after_insert(self):
if frappe.flags.in_test: if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self) make_dimension_in_accounting_doctypes(doc=self)
else: else:
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue="long") frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self)
def on_trash(self): def on_trash(self):
if frappe.flags.in_test: if frappe.flags.in_test:
delete_accounting_dimension(doc=self) delete_accounting_dimension(doc=self)
else: else:
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long") frappe.enqueue(delete_accounting_dimension, doc=self)
def set_fieldname_and_label(self): def set_fieldname_and_label(self):
if not self.label: if not self.label:
@@ -65,23 +48,17 @@ class AccountingDimension(Document):
if not self.fieldname: if not self.fieldname:
self.fieldname = scrub(self.label) self.fieldname = scrub(self.label)
def on_update(self): def make_dimension_in_accounting_doctypes(doc):
frappe.flags.accounting_dimensions = None doclist = get_doctypes_with_dimensions()
def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist:
doclist = get_doctypes_with_dimensions()
doc_count = len(get_accounting_dimensions()) doc_count = len(get_accounting_dimensions())
count = 0 count = 0
for doctype in doclist: for doctype in doclist:
if (doc_count + 1) % 2 == 0: if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break" insert_after_field = 'dimension_col_break'
else: else:
insert_after_field = "accounting_dimensions_section" insert_after_field = 'accounting_dimensions_section'
df = { df = {
"fieldname": doc.fieldname, "fieldname": doc.fieldname,
@@ -89,33 +66,26 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
"fieldtype": "Link", "fieldtype": "Link",
"options": doc.document_type, "options": doc.document_type,
"insert_after": insert_after_field, "insert_after": insert_after_field,
"owner": "Administrator", "owner": "Administrator"
} }
meta = frappe.get_meta(doctype, cached=False) if doctype == "Budget":
fieldnames = [d.fieldname for d in meta.get("fields")] add_dimension_to_budget_doctype(df, doc)
else:
if df["fieldname"] not in fieldnames: create_custom_field(doctype, df)
if doctype == "Budget":
add_dimension_to_budget_doctype(df.copy(), doc)
else:
create_custom_field(doctype, df, ignore_validate=True)
count += 1 count += 1
frappe.publish_progress(count * 100 / len(doclist), title=_("Creating Dimensions...")) frappe.publish_progress(count*100/len(doclist), title = _("Creating Dimensions..."))
frappe.clear_cache(doctype=doctype) frappe.clear_cache(doctype=doctype)
def add_dimension_to_budget_doctype(df, doc): def add_dimension_to_budget_doctype(df, doc):
df.update( df.update({
{ "insert_after": "cost_center",
"insert_after": "cost_center", "depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type)
"depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type), })
}
)
create_custom_field("Budget", df, ignore_validate=True) create_custom_field("Budget", df)
property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options") property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options")
@@ -124,44 +94,36 @@ def add_dimension_to_budget_doctype(df, doc):
property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type
property_setter_doc.save() property_setter_doc.save()
frappe.clear_cache(doctype="Budget") frappe.clear_cache(doctype='Budget')
else: else:
frappe.get_doc( frappe.get_doc({
{ "doctype": "Property Setter",
"doctype": "Property Setter", "doctype_or_field": "DocField",
"doctype_or_field": "DocField", "doc_type": "Budget",
"doc_type": "Budget", "field_name": "budget_against",
"field_name": "budget_against", "property": "options",
"property": "options", "property_type": "Text",
"property_type": "Text", "value": "\nCost Center\nProject\n" + doc.document_type
"value": "\nCost Center\nProject\n" + doc.document_type, }).insert(ignore_permissions=True)
}
).insert(ignore_permissions=True)
def delete_accounting_dimension(doc): def delete_accounting_dimension(doc):
doclist = get_doctypes_with_dimensions() doclist = get_doctypes_with_dimensions()
frappe.db.sql( frappe.db.sql("""
"""
DELETE FROM `tabCustom Field` DELETE FROM `tabCustom Field`
WHERE fieldname = %s WHERE fieldname = %s
AND dt IN (%s)""" AND dt IN (%s)""" % #nosec
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec ('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
tuple([doc.fieldname] + doclist),
)
frappe.db.sql( frappe.db.sql("""
"""
DELETE FROM `tabProperty Setter` DELETE FROM `tabProperty Setter`
WHERE field_name = %s WHERE field_name = %s
AND doc_type IN (%s)""" AND doc_type IN (%s)""" % #nosec
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec ('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
tuple([doc.fieldname] + doclist),
)
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options") budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
value_list = budget_against_property.value.split("\n")[3:] value_list = budget_against_property.value.split('\n')[3:]
if doc.document_type in value_list: if doc.document_type in value_list:
value_list.remove(doc.document_type) value_list.remove(doc.document_type)
@@ -172,7 +134,6 @@ def delete_accounting_dimension(doc):
for doctype in doclist: for doctype in doclist:
frappe.clear_cache(doctype=doctype) frappe.clear_cache(doctype=doctype)
@frappe.whitelist() @frappe.whitelist()
def disable_dimension(doc): def disable_dimension(doc):
if frappe.flags.in_test: if frappe.flags.in_test:
@@ -180,11 +141,10 @@ def disable_dimension(doc):
else: else:
frappe.enqueue(toggle_disabling, doc=doc) frappe.enqueue(toggle_disabling, doc=doc)
def toggle_disabling(doc): def toggle_disabling(doc):
doc = json.loads(doc) doc = json.loads(doc)
if doc.get("disabled"): if doc.get('disabled'):
df = {"read_only": 1} df = {"read_only": 1}
else: else:
df = {"read_only": 0} df = {"read_only": 0}
@@ -192,7 +152,7 @@ def toggle_disabling(doc):
doclist = get_doctypes_with_dimensions() doclist = get_doctypes_with_dimensions()
for doctype in doclist: for doctype in doclist:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get("fieldname")}) field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get('fieldname')})
if field: if field:
custom_field = frappe.get_doc("Custom Field", field) custom_field = frappe.get_doc("Custom Field", field)
custom_field.update(df) custom_field.update(df)
@@ -200,86 +160,46 @@ def toggle_disabling(doc):
frappe.clear_cache(doctype=doctype) frappe.clear_cache(doctype=doctype)
def get_doctypes_with_dimensions(): def get_doctypes_with_dimensions():
return frappe.get_hooks("accounting_dimension_doctypes") doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
"Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item",
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item",
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"]
return doclist
def get_accounting_dimensions(as_list=True, filters=None): def get_accounting_dimensions(as_list=True):
accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled"])
if not filters:
filters = {"disabled": 0}
if frappe.flags.accounting_dimensions is None:
frappe.flags.accounting_dimensions = frappe.get_all(
"Accounting Dimension",
fields=["label", "fieldname", "disabled", "document_type"],
filters=filters,
)
if as_list: if as_list:
return [d.fieldname for d in frappe.flags.accounting_dimensions] return [d.fieldname for d in accounting_dimensions]
else: else:
return frappe.flags.accounting_dimensions return accounting_dimensions
def get_checks_for_pl_and_bs_accounts(): def get_checks_for_pl_and_bs_accounts():
dimensions = frappe.db.sql( dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
WHERE p.name = c.parent""", WHERE p.name = c.parent""", as_dict=1)
as_dict=1,
)
return dimensions return dimensions
def get_dimension_with_children(doctype, dimensions):
if isinstance(dimensions, str):
dimensions = [dimensions]
all_dimensions = []
for dimension in dimensions:
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
children = frappe.get_all(
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
)
all_dimensions += [c.name for c in children]
return all_dimensions
@frappe.whitelist() @frappe.whitelist()
def get_dimensions(with_cost_center_and_project=False): def get_dimension_filters():
dimension_filters = frappe.db.sql( dimension_filters = frappe.db.sql("""
"""
SELECT label, fieldname, document_type SELECT label, fieldname, document_type
FROM `tabAccounting Dimension` FROM `tabAccounting Dimension`
WHERE disabled = 0 WHERE disabled = 0
""", """, as_dict=1)
as_dict=1,
)
default_dimensions = frappe.db.sql( default_dimensions = frappe.db.sql("""SELECT parent, company, default_dimension
"""SELECT p.fieldname, c.company, c.default_dimension FROM `tabAccounting Dimension Detail`""", as_dict=1)
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
WHERE c.parent = p.name""",
as_dict=1,
)
if with_cost_center_and_project:
dimension_filters.extend(
[
{"fieldname": "cost_center", "document_type": "Cost Center"},
{"fieldname": "project", "document_type": "Project"},
]
)
default_dimensions_map = {} default_dimensions_map = {}
for dimension in default_dimensions: for dimension in default_dimensions:
default_dimensions_map.setdefault(dimension.company, {}) default_dimensions_map.setdefault(dimension['company'], {})
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension default_dimensions_map[dimension['company']][dimension['parent']] = dimension['default_dimension']
return dimension_filters, default_dimensions_map return dimension_filters, default_dimensions_map

View File

@@ -1,45 +1,70 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
from __future__ import unicode_literals
import unittest
import frappe import frappe
import unittest
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
test_dependencies = ["Cost Center", "Location", "Warehouse", "Department"] from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import delete_accounting_dimension
class TestAccountingDimension(unittest.TestCase): class TestAccountingDimension(unittest.TestCase):
def setUp(self): def setUp(self):
create_dimension() frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
dimension = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Department",
}).insert()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
dimension1.disabled = 0
dimension1.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
dimension1 = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Location",
})
dimension1.append("dimension_defaults", {
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1
})
dimension1.insert()
dimension1.save()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
dimension1.disabled = 0
dimension1.save()
def test_dimension_against_sales_invoice(self): def test_dimension_against_sales_invoice(self):
si = create_sales_invoice(do_not_save=1) si = create_sales_invoice(do_not_save=1)
si.location = "Block 1" si.location = "Block 1"
si.append( si.append("items", {
"items", "item_code": "_Test Item",
{ "warehouse": "_Test Warehouse - _TC",
"item_code": "_Test Item", "qty": 1,
"warehouse": "_Test Warehouse - _TC", "rate": 100,
"qty": 1, "income_account": "Sales - _TC",
"rate": 100, "expense_account": "Cost of Goods Sold - _TC",
"income_account": "Sales - _TC", "cost_center": "_Test Cost Center - _TC",
"expense_account": "Cost of Goods Sold - _TC", "department": "_Test Department - _TC",
"cost_center": "_Test Cost Center - _TC", "location": "Block 1"
"department": "_Test Department - _TC", })
"location": "Block 1",
},
)
si.save() si.save()
si.submit() si.submit()
gle = frappe.get_doc("GL Entry", {"voucher_no": si.name, "account": "Sales - _TC"}) gle = frappe.get_doc("GL Entry", {"voucher_no": si.name, "account": "Sales - _TC"})
self.assertEqual(gle.get("department"), "_Test Department - _TC") self.assertEqual(gle.get('department'), "_Test Department - _TC")
def test_dimension_against_journal_entry(self): def test_dimension_against_journal_entry(self):
je = make_journal_entry("Sales - _TC", "Sales Expenses - _TC", 500, save=False) je = make_journal_entry("Sales - _TC", "Sales Expenses - _TC", 500, save=False)
@@ -54,24 +79,21 @@ class TestAccountingDimension(unittest.TestCase):
gle = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales - _TC"}) gle = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales - _TC"})
gle1 = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales Expenses - _TC"}) gle1 = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales Expenses - _TC"})
self.assertEqual(gle.get("department"), "_Test Department - _TC") self.assertEqual(gle.get('department'), "_Test Department - _TC")
self.assertEqual(gle1.get("department"), "_Test Department - _TC") self.assertEqual(gle1.get('department'), "_Test Department - _TC")
def test_mandatory(self): def test_mandatory(self):
si = create_sales_invoice(do_not_save=1) si = create_sales_invoice(do_not_save=1)
si.append( si.append("items", {
"items", "item_code": "_Test Item",
{ "warehouse": "_Test Warehouse - _TC",
"item_code": "_Test Item", "qty": 1,
"warehouse": "_Test Warehouse - _TC", "rate": 100,
"qty": 1, "income_account": "Sales - _TC",
"rate": 100, "expense_account": "Cost of Goods Sold - _TC",
"income_account": "Sales - _TC", "cost_center": "_Test Cost Center - _TC",
"expense_account": "Cost of Goods Sold - _TC", "location": ""
"cost_center": "_Test Cost Center - _TC", })
"location": "",
},
)
si.save() si.save()
self.assertRaises(frappe.ValidationError, si.submit) self.assertRaises(frappe.ValidationError, si.submit)
@@ -80,47 +102,6 @@ class TestAccountingDimension(unittest.TestCase):
disable_dimension() disable_dimension()
def create_dimension():
frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Department",
}
).insert()
else:
dimension = frappe.get_doc("Accounting Dimension", "Department")
dimension.disabled = 0
dimension.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
dimension1 = frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Location",
}
)
dimension1.append(
"dimension_defaults",
{
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1,
},
)
dimension1.insert()
dimension1.save()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
dimension1.disabled = 0
dimension1.save()
def disable_dimension(): def disable_dimension():
dimension1 = frappe.get_doc("Accounting Dimension", "Department") dimension1 = frappe.get_doc("Accounting Dimension", "Department")
dimension1.disabled = 1 dimension1.disabled = 1
@@ -129,3 +110,5 @@ def disable_dimension():
dimension2 = frappe.get_doc("Accounting Dimension", "Location") dimension2 = frappe.get_doc("Accounting Dimension", "Location")
dimension2.disabled = 1 dimension2.disabled = 1
dimension2.save() dimension2.save()

View File

@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class AccountingDimensionDetail(Document): class AccountingDimensionDetail(Document):
pass pass

View File

@@ -1,82 +0,0 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Accounting Dimension Filter', {
refresh: function(frm, cdt, cdn) {
if (frm.doc.accounting_dimension) {
frm.set_df_property('dimensions', 'label', frm.doc.accounting_dimension, cdn, 'dimension_value');
}
let help_content =
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
<tr><td>
<p>
<i class="fa fa-hand-right"></i>
{{__('Note: On checking Is Mandatory the accounting dimension will become mandatory against that specific account for all accounting transactions')}}
</p>
</td></tr>
</table>`;
frm.set_df_property('dimension_filter_help', 'options', help_content);
},
onload: function(frm) {
frm.set_query('applicable_on_account', 'accounts', function() {
return {
filters: {
'company': frm.doc.company
}
};
});
frappe.db.get_list('Accounting Dimension',
{fields: ['document_type']}).then((res) => {
let options = ['Cost Center', 'Project'];
res.forEach((dimension) => {
options.push(dimension.document_type);
});
frm.set_df_property('accounting_dimension', 'options', options);
});
frm.trigger('setup_filters');
},
setup_filters: function(frm) {
let filters = {};
if (frm.doc.accounting_dimension) {
frappe.model.with_doctype(frm.doc.accounting_dimension, function() {
if (frappe.model.is_tree(frm.doc.accounting_dimension)) {
filters['is_group'] = 0;
}
if (frappe.meta.has_field(frm.doc.accounting_dimension, 'company')) {
filters['company'] = frm.doc.company;
}
frm.set_query('dimension_value', 'dimensions', function() {
return {
filters: filters
};
});
});
}
},
accounting_dimension: function(frm) {
frm.clear_table("dimensions");
let row = frm.add_child("dimensions");
row.accounting_dimension = frm.doc.accounting_dimension;
frm.refresh_field("dimensions");
frm.trigger('setup_filters');
},
});
frappe.ui.form.on('Allowed Dimension', {
dimensions_add: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
row.accounting_dimension = frm.doc.accounting_dimension;
frm.refresh_field("dimensions");
}
});

View File

@@ -1,158 +0,0 @@
{
"actions": [],
"autoname": "format:{accounting_dimension}-{#####}",
"creation": "2020-11-08 18:28:11.906146",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"accounting_dimension",
"disabled",
"column_break_2",
"company",
"allow_or_restrict",
"section_break_4",
"accounts",
"column_break_6",
"dimensions",
"section_break_10",
"dimension_filter_help"
],
"fields": [
{
"fieldname": "accounting_dimension",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Accounting Dimension",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"hide_border": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "allow_or_restrict",
"fieldtype": "Select",
"label": "Allow Or Restrict Dimension",
"options": "Allow\nRestrict",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "accounts",
"fieldtype": "Table",
"label": "Applicable On Account",
"options": "Applicable On Account",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.accounting_dimension",
"fieldname": "dimensions",
"fieldtype": "Table",
"label": "Applicable Dimension",
"options": "Allowed Dimension",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "dimension_filter_help",
"fieldtype": "HTML",
"label": "Dimension Filter Help",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-02-03 12:04:58.678402",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Dimension Filter",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -1,79 +0,0 @@
# Copyright, (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _, scrub
from frappe.model.document import Document
class AccountingDimensionFilter(Document):
def validate(self):
self.validate_applicable_accounts()
def validate_applicable_accounts(self):
accounts = frappe.db.sql(
"""
SELECT a.applicable_on_account as account
FROM `tabApplicable On Account` a, `tabAccounting Dimension Filter` d
WHERE d.name = a.parent
and d.name != %s
and d.accounting_dimension = %s
""",
(self.name, self.accounting_dimension),
as_dict=1,
)
account_list = [d.account for d in accounts]
for account in self.get("accounts"):
if account.applicable_on_account in account_list:
frappe.throw(
_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
account.idx,
frappe.bold(account.applicable_on_account),
frappe.bold(self.accounting_dimension),
)
)
def get_dimension_filter_map():
filters = frappe.db.sql(
"""
SELECT
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
p.allow_or_restrict, a.is_mandatory
FROM
`tabApplicable On Account` a, `tabAllowed Dimension` d,
`tabAccounting Dimension Filter` p
WHERE
p.name = a.parent
AND p.disabled = 0
AND p.name = d.parent
""",
as_dict=1,
)
dimension_filter_map = {}
for f in filters:
f.fieldname = scrub(f.accounting_dimension)
build_map(
dimension_filter_map,
f.fieldname,
f.applicable_on_account,
f.dimension_value,
f.allow_or_restrict,
f.is_mandatory,
)
return dimension_filter_map
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
map_object.setdefault(
(dimension, account),
{"allowed_dimensions": [], "is_mandatory": is_mandatory, "allow_or_restrict": allow_or_restrict},
)
map_object[(dimension, account)]["allowed_dimensions"].append(filter_value)

View File

@@ -1,106 +0,0 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension,
disable_dimension,
)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
test_dependencies = ["Location", "Cost Center", "Department"]
class TestAccountingDimensionFilter(unittest.TestCase):
def setUp(self):
create_dimension()
create_accounting_dimension_filter()
self.invoice_list = []
def test_allowed_dimension_validation(self):
si = create_sales_invoice(do_not_save=1)
si.items[0].cost_center = "Main - _TC"
si.department = "Accounts - _TC"
si.location = "Block 1"
si.save()
self.assertRaises(InvalidAccountDimensionError, si.submit)
self.invoice_list.append(si)
def test_mandatory_dimension_validation(self):
si = create_sales_invoice(do_not_save=1)
si.department = ""
si.location = "Block 1"
# Test with no department for Sales Account
si.items[0].department = ""
si.items[0].cost_center = "_Test Cost Center 2 - _TC"
si.save()
self.assertRaises(MandatoryAccountDimensionError, si.submit)
self.invoice_list.append(si)
def tearDown(self):
disable_dimension_filter()
disable_dimension()
for si in self.invoice_list:
si.load_from_db()
if si.docstatus == 1:
si.cancel()
def create_accounting_dimension_filter():
if not frappe.db.get_value(
"Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}
):
frappe.get_doc(
{
"doctype": "Accounting Dimension Filter",
"accounting_dimension": "Cost Center",
"allow_or_restrict": "Allow",
"company": "_Test Company",
"accounts": [
{
"applicable_on_account": "Sales - _TC",
}
],
"dimensions": [
{"accounting_dimension": "Cost Center", "dimension_value": "_Test Cost Center 2 - _TC"}
],
}
).insert()
else:
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"})
doc.disabled = 0
doc.save()
if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Department"}):
frappe.get_doc(
{
"doctype": "Accounting Dimension Filter",
"accounting_dimension": "Department",
"allow_or_restrict": "Allow",
"company": "_Test Company",
"accounts": [{"applicable_on_account": "Sales - _TC", "is_mandatory": 1}],
"dimensions": [{"accounting_dimension": "Department", "dimension_value": "Accounts - _TC"}],
}
).insert()
else:
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Department"})
doc.disabled = 0
doc.save()
def disable_dimension_filter():
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"})
doc.disabled = 1
doc.save()
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Department"})
doc.disabled = 1
doc.save()

View File

@@ -1,15 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import frappe import frappe
from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _
class OverlapError(frappe.ValidationError): pass
class OverlapError(frappe.ValidationError):
pass
class AccountingPeriod(Document): class AccountingPeriod(Document):
def validate(self): def validate(self):
@@ -19,12 +17,11 @@ class AccountingPeriod(Document):
self.bootstrap_doctypes_for_closing() self.bootstrap_doctypes_for_closing()
def autoname(self): def autoname(self):
company_abbr = frappe.get_cached_value("Company", self.company, "abbr") company_abbr = frappe.get_cached_value('Company', self.company, "abbr")
self.name = " - ".join([self.period_name, company_abbr]) self.name = " - ".join([self.period_name, company_abbr])
def validate_overlap(self): def validate_overlap(self):
existing_accounting_period = frappe.db.sql( existing_accounting_period = frappe.db.sql("""select name from `tabAccounting Period`
"""select name from `tabAccounting Period`
where ( where (
(%(start_date)s between start_date and end_date) (%(start_date)s between start_date and end_date)
or (%(end_date)s between start_date and end_date) or (%(end_date)s between start_date and end_date)
@@ -35,29 +32,17 @@ class AccountingPeriod(Document):
"start_date": self.start_date, "start_date": self.start_date,
"end_date": self.end_date, "end_date": self.end_date,
"name": self.name, "name": self.name,
"company": self.company, "company": self.company
}, }, as_dict=True)
as_dict=True,
)
if len(existing_accounting_period) > 0: if len(existing_accounting_period) > 0:
frappe.throw( frappe.throw(_("Accounting Period overlaps with {0}")
_("Accounting Period overlaps with {0}").format(existing_accounting_period[0].get("name")), .format(existing_accounting_period[0].get("name")), OverlapError)
OverlapError,
)
@frappe.whitelist()
def get_doctypes_for_closing(self): def get_doctypes_for_closing(self):
docs_for_closing = [] docs_for_closing = []
doctypes = [ doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", "Bank Reconciliation",
"Sales Invoice", "Asset", "Purchase Order", "Sales Order", "Leave Application", "Leave Allocation", "Stock Entry"]
"Purchase Invoice",
"Journal Entry",
"Payroll Entry",
"Bank Clearance",
"Asset",
"Stock Entry",
]
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes] closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes: for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype) docs_for_closing.append(closed_doctype)
@@ -67,7 +52,7 @@ class AccountingPeriod(Document):
def bootstrap_doctypes_for_closing(self): def bootstrap_doctypes_for_closing(self):
if len(self.closed_documents) == 0: if len(self.closed_documents) == 0:
for doctype_for_closing in self.get_doctypes_for_closing(): for doctype_for_closing in self.get_doctypes_for_closing():
self.append( self.append('closed_documents', {
"closed_documents", "document_type": doctype_for_closing.document_type,
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed}, "closed": doctype_for_closing.closed
) })

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Accounting Period", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Accounting Period
() => frappe.tests.make('Accounting Period', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,55 +1,46 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
from __future__ import unicode_literals
import unittest
import frappe import frappe
from frappe.utils import add_months, nowdate 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.accounting_period.accounting_period import OverlapError
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
test_dependencies = ["Item"]
class TestAccountingPeriod(unittest.TestCase): class TestAccountingPeriod(unittest.TestCase):
def test_overlap(self): def test_overlap(self):
ap1 = create_accounting_period( ap1 = create_accounting_period(start_date = "2018-04-01",
start_date="2018-04-01", end_date="2018-06-30", company="Wind Power LLC" end_date = "2018-06-30", company = "Wind Power LLC")
) ap1.save()
ap1.save()
ap2 = create_accounting_period( ap2 = create_accounting_period(start_date = "2018-06-30",
start_date="2018-06-30", end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
end_date="2018-07-10", self.assertRaises(OverlapError, ap2.save)
company="Wind Power LLC",
period_name="Test Accounting Period 1",
)
self.assertRaises(OverlapError, ap2.save)
def test_accounting_period(self): def test_accounting_period(self):
ap1 = create_accounting_period(period_name="Test Accounting Period 2") ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
ap1.save() ap1.save()
doc = create_sales_invoice( doc = create_sales_invoice(do_not_submit=1, cost_center = "_Test Company - _TC", warehouse = "Stores - _TC")
do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC" self.assertRaises(ClosedAccountingPeriod, doc.submit)
)
self.assertRaises(ClosedAccountingPeriod, doc.submit)
def tearDown(self):
for d in frappe.get_all("Accounting Period"):
frappe.delete_doc("Accounting Period", d.name)
def tearDown(self):
for d in frappe.get_all("Accounting Period"):
frappe.delete_doc("Accounting Period", d.name)
def create_accounting_period(**args): def create_accounting_period(**args):
args = frappe._dict(args) args = frappe._dict(args)
accounting_period = frappe.new_doc("Accounting Period") accounting_period = frappe.new_doc("Accounting Period")
accounting_period.start_date = args.start_date or nowdate() accounting_period.start_date = args.start_date or nowdate()
accounting_period.end_date = args.end_date or add_months(nowdate(), 1) accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
accounting_period.company = args.company or "_Test Company" accounting_period.company = args.company or "_Test Company"
accounting_period.period_name = args.period_name or "_Test_Period_Name_1" accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
accounting_period.append("closed_documents", {"document_type": "Sales Invoice", "closed": 1}) accounting_period.append("closed_documents", {
"document_type": 'Sales Invoice', "closed": 1
})
return accounting_period return accounting_period

View File

@@ -6,46 +6,3 @@ frappe.ui.form.on('Accounts Settings', {
} }
}); });
frappe.tour['Accounts Settings'] = [
{
fieldname: "acc_frozen_upto",
title: "Accounts Frozen Upto",
description: __("Freeze accounting transactions up to specified date, nobody can make/modify entry except the specified Role."),
},
{
fieldname: "frozen_accounts_modifier",
title: "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
description: __("Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.")
},
{
fieldname: "determine_address_tax_category_from",
title: "Determine Address Tax Category From",
description: __("Tax category can be set on Addresses. An address can be Shipping or Billing address. Set which addres to select when applying Tax Category.")
},
{
fieldname: "over_billing_allowance",
title: "Over Billing Allowance Percentage",
description: __("The percentage by which you can overbill transactions. For example, if the order value is $100 for an Item and percentage here is set as 10% then you are allowed to bill for $110.")
},
{
fieldname: "credit_controller",
title: "Credit Controller",
description: __("Select the role that is allowed to submit transactions that exceed credit limits set. The credit limit can be set in the Customer form.")
},
{
fieldname: "make_payment_via_journal_entry",
title: "Make Payment via Journal Entry",
description: __("When checked, if user proceeds to make payment from an invoice, the system will open a Journal Entry instead of a Payment Entry.")
},
{
fieldname: "unlink_payment_on_cancellation_of_invoice",
title: "Unlink Payment on Cancellation of Invoice",
description: __("If checked, system will unlink the payment against the respective invoice.")
},
{
fieldname: "unlink_advance_payment_on_cancelation_of_order",
title: "Unlink Advance Payment on Cancellation of Order",
description: __("Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.")
}
];

View File

@@ -1,322 +1,210 @@
{ {
"actions": [], "creation": "2013-06-24 15:49:57",
"creation": "2013-06-24 15:49:57", "description": "Settings for Accounts",
"description": "Settings for Accounts", "doctype": "DocType",
"doctype": "DocType", "document_type": "Other",
"document_type": "Other", "editable_grid": 1,
"editable_grid": 1, "engine": "InnoDB",
"engine": "InnoDB", "field_order": [
"field_order": [ "auto_accounting_for_stock",
"accounts_transactions_settings_section", "acc_frozen_upto",
"over_billing_allowance", "frozen_accounts_modifier",
"role_allowed_to_over_bill", "determine_address_tax_category_from",
"make_payment_via_journal_entry", "over_billing_allowance",
"column_break_11", "column_break_4",
"check_supplier_invoice_uniqueness", "credit_controller",
"unlink_payment_on_cancellation_of_invoice", "check_supplier_invoice_uniqueness",
"automatically_fetch_payment_terms", "make_payment_via_journal_entry",
"delete_linked_ledger_entries", "unlink_payment_on_cancellation_of_invoice",
"book_asset_depreciation_entry_automatically", "unlink_advance_payment_on_cancelation_of_order",
"unlink_advance_payment_on_cancelation_of_order", "book_asset_depreciation_entry_automatically",
"enable_common_party_accounting", "allow_cost_center_in_entry_of_bs_account",
"allow_multi_currency_invoices_against_single_party_account", "add_taxes_from_item_tax_template",
"post_change_gl_entries", "automatically_fetch_payment_terms",
"enable_discount_accounting", "print_settings",
"tax_settings_section", "show_inclusive_tax_in_print",
"determine_address_tax_category_from", "column_break_12",
"column_break_19", "show_payment_schedule_in_print",
"add_taxes_from_item_tax_template", "currency_exchange_section",
"period_closing_settings_section", "allow_stale",
"acc_frozen_upto", "stale_days",
"frozen_accounts_modifier", "report_settings_sb",
"column_break_4", "use_custom_cash_flow"
"credit_controller", ],
"deferred_accounting_settings_section", "fields": [
"book_deferred_entries_based_on", {
"column_break_18", "default": "1",
"automatically_process_deferred_accounting_entry", "description": "If enabled, the system will post accounting entries for inventory automatically.",
"book_deferred_entries_via_journal_entry", "fieldname": "auto_accounting_for_stock",
"submit_journal_entries", "fieldtype": "Check",
"print_settings", "hidden": 1,
"show_inclusive_tax_in_print", "in_list_view": 1,
"column_break_12", "label": "Make Accounting Entry For Every Stock Movement"
"show_payment_schedule_in_print", },
"currency_exchange_section", {
"allow_stale", "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
"stale_days", "fieldname": "acc_frozen_upto",
"report_settings_sb", "fieldtype": "Date",
"use_custom_cash_flow" "in_list_view": 1,
], "label": "Accounts Frozen Upto"
"fields": [ },
{ {
"description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below", "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
"fieldname": "acc_frozen_upto", "fieldname": "frozen_accounts_modifier",
"fieldtype": "Date", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Accounts Frozen Till Date" "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
}, "options": "Role"
{ },
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", {
"fieldname": "frozen_accounts_modifier", "default": "Billing Address",
"fieldtype": "Link", "description": "Address used to determine Tax Category in transactions.",
"in_list_view": 1, "fieldname": "determine_address_tax_category_from",
"label": "Role Allowed to Set Frozen Accounts and Edit Frozen Entries", "fieldtype": "Select",
"options": "Role" "label": "Determine Address Tax Category From",
}, "options": "Billing Address\nShipping Address"
{ },
"default": "Billing Address", {
"description": "Address used to determine Tax Category in transactions", "fieldname": "column_break_4",
"fieldname": "determine_address_tax_category_from", "fieldtype": "Column Break"
"fieldtype": "Select", },
"label": "Determine Address Tax Category From", {
"options": "Billing Address\nShipping Address" "description": "Role that is allowed to submit transactions that exceed credit limits set.",
}, "fieldname": "credit_controller",
{ "fieldtype": "Link",
"fieldname": "column_break_4", "in_list_view": 1,
"fieldtype": "Column Break" "label": "Credit Controller",
}, "options": "Role"
{ },
"description": "This role is allowed to submit transactions that exceed credit limits", {
"fieldname": "credit_controller", "fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Link", "fieldtype": "Check",
"in_list_view": 1, "label": "Check Supplier Invoice Number Uniqueness"
"label": "Credit Controller", },
"options": "Role" {
}, "fieldname": "make_payment_via_journal_entry",
{ "fieldtype": "Check",
"default": "0", "label": "Make Payment via Journal Entry"
"fieldname": "check_supplier_invoice_uniqueness", },
"fieldtype": "Check", {
"label": "Check Supplier Invoice Number Uniqueness" "default": "1",
}, "fieldname": "unlink_payment_on_cancellation_of_invoice",
{ "fieldtype": "Check",
"default": "0", "label": "Unlink Payment on Cancellation of Invoice"
"fieldname": "make_payment_via_journal_entry", },
"fieldtype": "Check", {
"hidden": 1, "default": "1",
"label": "Make Payment via Journal Entry" "fieldname": "unlink_advance_payment_on_cancelation_of_order",
}, "fieldtype": "Check",
{ "label": "Unlink Advance Payment on Cancelation of Order"
"default": "1", },
"fieldname": "unlink_payment_on_cancellation_of_invoice", {
"fieldtype": "Check", "default": "1",
"label": "Unlink Payment on Cancellation of Invoice" "fieldname": "book_asset_depreciation_entry_automatically",
}, "fieldtype": "Check",
{ "label": "Book Asset Depreciation Entry Automatically"
"default": "1", },
"fieldname": "unlink_advance_payment_on_cancelation_of_order", {
"fieldtype": "Check", "fieldname": "allow_cost_center_in_entry_of_bs_account",
"label": "Unlink Advance Payment on Cancellation of Order" "fieldtype": "Check",
}, "label": "Allow Cost Center In Entry of Balance Sheet Account"
{ },
"default": "1", {
"fieldname": "book_asset_depreciation_entry_automatically", "default": "1",
"fieldtype": "Check", "fieldname": "add_taxes_from_item_tax_template",
"label": "Book Asset Depreciation Entry Automatically" "fieldtype": "Check",
}, "label": "Automatically Add Taxes and Charges from Item Tax Template"
{ },
"default": "1", {
"fieldname": "add_taxes_from_item_tax_template", "fieldname": "print_settings",
"fieldtype": "Check", "fieldtype": "Section Break",
"label": "Automatically Add Taxes and Charges from Item Tax Template" "label": "Print Settings"
}, },
{ {
"fieldname": "print_settings", "fieldname": "show_inclusive_tax_in_print",
"fieldtype": "Section Break", "fieldtype": "Check",
"label": "Print Settings" "label": "Show Inclusive Tax In Print"
}, },
{ {
"default": "0", "fieldname": "column_break_12",
"fieldname": "show_inclusive_tax_in_print", "fieldtype": "Column Break"
"fieldtype": "Check", },
"label": "Show Inclusive Tax in Print" {
}, "fieldname": "show_payment_schedule_in_print",
{ "fieldtype": "Check",
"fieldname": "column_break_12", "label": "Show Payment Schedule in Print"
"fieldtype": "Column Break" },
}, {
{ "fieldname": "currency_exchange_section",
"default": "0", "fieldtype": "Section Break",
"fieldname": "show_payment_schedule_in_print", "label": "Currency Exchange Settings"
"fieldtype": "Check", },
"label": "Show Payment Schedule in Print" {
}, "default": "1",
{ "fieldname": "allow_stale",
"fieldname": "currency_exchange_section", "fieldtype": "Check",
"fieldtype": "Section Break", "in_list_view": 1,
"label": "Currency Exchange Settings" "label": "Allow Stale Exchange Rates"
}, },
{ {
"default": "1", "default": "1",
"fieldname": "allow_stale", "depends_on": "eval:doc.allow_stale==0",
"fieldtype": "Check", "fieldname": "stale_days",
"in_list_view": 1, "fieldtype": "Int",
"label": "Allow Stale Exchange Rates" "label": "Stale Days"
}, },
{ {
"default": "1", "fieldname": "report_settings_sb",
"depends_on": "eval:doc.allow_stale==0", "fieldtype": "Section Break",
"fieldname": "stale_days", "label": "Report Settings"
"fieldtype": "Int", },
"label": "Stale Days" {
}, "default": "0",
{ "description": "Only select if you have setup Cash Flow Mapper documents",
"fieldname": "report_settings_sb", "fieldname": "use_custom_cash_flow",
"fieldtype": "Section Break", "fieldtype": "Check",
"label": "Report Settings" "label": "Use Custom Cash Flow Format"
}, },
{ {
"default": "0", "fieldname": "automatically_fetch_payment_terms",
"description": "Only select this if you have set up the Cash Flow Mapper documents", "fieldtype": "Check",
"fieldname": "use_custom_cash_flow", "label": "Automatically Fetch Payment Terms"
"fieldtype": "Check", },
"label": "Use Custom Cash Flow Format" {
}, "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.",
{ "fieldname": "over_billing_allowance",
"default": "0", "fieldtype": "Currency",
"fieldname": "automatically_fetch_payment_terms", "label": "Over Billing Allowance (%)"
"fieldtype": "Check", }
"label": "Automatically Fetch Payment Terms from Order" ],
}, "icon": "icon-cog",
{ "idx": 1,
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ", "issingle": 1,
"fieldname": "over_billing_allowance", "modified": "2019-07-04 18:20:55.789946",
"fieldtype": "Currency", "modified_by": "Administrator",
"label": "Over Billing Allowance (%)" "module": "Accounts",
}, "name": "Accounts Settings",
{ "owner": "Administrator",
"default": "1", "permissions": [
"fieldname": "automatically_process_deferred_accounting_entry", {
"fieldtype": "Check", "create": 1,
"label": "Automatically Process Deferred Accounting Entry" "email": 1,
}, "print": 1,
{ "read": 1,
"fieldname": "deferred_accounting_settings_section", "role": "Accounts Manager",
"fieldtype": "Section Break", "share": 1,
"label": "Deferred Accounting Settings" "write": 1
}, },
{ {
"fieldname": "column_break_18", "read": 1,
"fieldtype": "Column Break" "role": "Sales User"
}, },
{ {
"default": "0", "read": 1,
"description": "If this is unchecked, direct GL entries will be created to book deferred revenue or expense", "role": "Purchase User"
"fieldname": "book_deferred_entries_via_journal_entry", }
"fieldtype": "Check", ],
"label": "Book Deferred Entries Via Journal Entry" "quick_entry": 1,
}, "sort_order": "ASC",
{ "track_changes": 1
"default": "0",
"depends_on": "eval:doc.book_deferred_entries_via_journal_entry",
"description": "If this is unchecked Journal Entries will be saved in a Draft state and will have to be submitted manually",
"fieldname": "submit_journal_entries",
"fieldtype": "Check",
"label": "Submit Journal Entries"
},
{
"default": "Days",
"description": "If \"Months\" is selected, a fixed amount will be booked as deferred revenue or expense for each month irrespective of the number of days in a month. It will be prorated if deferred revenue or expense is not booked for an entire month",
"fieldname": "book_deferred_entries_based_on",
"fieldtype": "Select",
"label": "Book Deferred Entries Based On",
"options": "Days\nMonths"
},
{
"default": "0",
"fieldname": "delete_linked_ledger_entries",
"fieldtype": "Check",
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
},
{
"description": "Users with this role are allowed to over bill above the allowance percentage",
"fieldname": "role_allowed_to_over_bill",
"fieldtype": "Link",
"label": "Role Allowed to Over Bill ",
"options": "Role"
},
{
"fieldname": "period_closing_settings_section",
"fieldtype": "Section Break",
"label": "Period Closing Settings"
},
{
"fieldname": "accounts_transactions_settings_section",
"fieldtype": "Section Break",
"label": "Transactions Settings"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "tax_settings_section",
"fieldtype": "Section Break",
"label": "Tax Settings"
},
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
},
{
"default": "1",
"description": "If enabled, ledger entries will be posted for change amount in POS transactions",
"fieldname": "post_change_gl_entries",
"fieldtype": "Check",
"label": "Create Ledger Entries for Change Amount"
},
{
"default": "0",
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
"fieldname": "enable_discount_accounting",
"fieldtype": "Check",
"label": "Enable Discount Accounting"
},
{
"default": "0",
"fieldname": "enable_common_party_accounting",
"fieldtype": "Check",
"label": "Enable Common Party Accounting"
},
{
"default": "0",
"description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency",
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
"fieldtype": "Check",
"label": "Allow multi-currency invoices against single party account"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-07-11 13:37:50.605141",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "Sales User"
},
{
"read": 1,
"role": "Purchase User"
} }
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}

View File

@@ -3,14 +3,11 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import frappe import frappe
from frappe import _
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.model.document import Document
from frappe.utils import cint from frappe.utils import cint
from frappe.model.document import Document
from erpnext.stock.utils import check_pending_reposting from frappe.custom.doctype.property_setter.property_setter import make_property_setter
class AccountsSettings(Document): class AccountsSettings(Document):
@@ -18,108 +15,26 @@ class AccountsSettings(Document):
frappe.clear_cache() frappe.clear_cache()
def validate(self): def validate(self):
frappe.db.set_default( frappe.db.set_default("add_taxes_from_item_tax_template",
"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0) self.get("add_taxes_from_item_tax_template", 0))
)
frappe.db.set_default(
"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
)
self.validate_stale_days() self.validate_stale_days()
self.enable_payment_schedule_in_print() self.enable_payment_schedule_in_print()
self.toggle_discount_accounting_fields() self.enable_fields_for_cost_center_settings()
self.validate_pending_reposts()
def validate_stale_days(self): def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0: if not self.allow_stale and cint(self.stale_days) <= 0:
frappe.msgprint( frappe.msgprint(
_("Stale Days should start from 1."), title="Error", indicator="red", raise_exception=1 "Stale Days should start from 1.", title='Error', indicator='red',
) raise_exception=1)
def enable_payment_schedule_in_print(self): def enable_payment_schedule_in_print(self):
show_in_print = cint(self.show_payment_schedule_in_print) show_in_print = cint(self.show_payment_schedule_in_print)
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"): for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
make_property_setter( make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check")
doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check")
)
make_property_setter(
doctype,
"payment_schedule",
"print_hide",
0 if show_in_print else 1,
"Check",
validate_fields_for_doctype=False,
)
def toggle_discount_accounting_fields(self): def enable_fields_for_cost_center_settings(self):
enable_discount_accounting = cint(self.enable_discount_accounting) show_field = 0 if cint(self.allow_cost_center_in_entry_of_bs_account) else 1
for doctype in ("Sales Invoice", "Purchase Invoice", "Payment Entry"):
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]: make_property_setter(doctype, "cost_center", "hidden", show_field, "Check")
make_property_setter(
doctype,
"discount_account",
"hidden",
not (enable_discount_accounting),
"Check",
validate_fields_for_doctype=False,
)
if enable_discount_accounting:
make_property_setter(
doctype,
"discount_account",
"mandatory_depends_on",
"eval: doc.discount_amount",
"Code",
validate_fields_for_doctype=False,
)
else:
make_property_setter(
doctype,
"discount_account",
"mandatory_depends_on",
"",
"Code",
validate_fields_for_doctype=False,
)
for doctype in ["Sales Invoice", "Purchase Invoice"]:
make_property_setter(
doctype,
"additional_discount_account",
"hidden",
not (enable_discount_accounting),
"Check",
validate_fields_for_doctype=False,
)
if enable_discount_accounting:
make_property_setter(
doctype,
"additional_discount_account",
"mandatory_depends_on",
"eval: doc.discount_amount",
"Code",
validate_fields_for_doctype=False,
)
else:
make_property_setter(
doctype,
"additional_discount_account",
"mandatory_depends_on",
"",
"Code",
validate_fields_for_doctype=False,
)
make_property_setter(
"Item",
"default_discount_account",
"hidden",
not (enable_discount_accounting),
"Check",
validate_fields_for_doctype=False,
)
def validate_pending_reposts(self):
if self.acc_frozen_upto:
check_pending_reposting(self.acc_frozen_upto)

View File

@@ -1,8 +0,0 @@
frappe.ui.form.on('Accounts Settings', {
refresh: function(frm) {
frm.set_df_property("acc_frozen_upto", "label", "Books Closed Through");
frm.set_df_property("frozen_accounts_modifier", "label", "Role Allowed to Close Books & Make Changes to Closed Periods");
frm.set_df_property("credit_controller", "label", "Credit Manager");
}
});

View File

@@ -1,3 +1,4 @@
from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
@@ -7,12 +8,12 @@ class TestAccountsSettings(unittest.TestCase):
def tearDown(self): def tearDown(self):
# Just in case `save` method succeeds, we need to take things back to default so that other tests # Just in case `save` method succeeds, we need to take things back to default so that other tests
# don't break # don't break
cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
cur_settings.allow_stale = 1 cur_settings.allow_stale = 1
cur_settings.save() cur_settings.save()
def test_stale_days(self): def test_stale_days(self):
cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings") cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
cur_settings.allow_stale = 0 cur_settings.allow_stale = 0
cur_settings.stale_days = 0 cur_settings.stale_days = 0

View File

@@ -1,56 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-11-25 10:24:39.836195",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"reference_type",
"reference_name",
"reference_detail",
"account_head",
"allocated_amount"
],
"fields": [
{
"fieldname": "reference_type",
"fieldtype": "Link",
"label": "Reference Type",
"options": "DocType"
},
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"label": "Reference Name",
"options": "reference_type"
},
{
"fieldname": "reference_detail",
"fieldtype": "Data",
"label": "Reference Detail"
},
{
"fieldname": "account_head",
"fieldtype": "Link",
"label": "Account Head",
"options": "Account"
},
{
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"label": "Allocated Amount",
"options": "party_account_currency"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-11-25 10:27:51.712286",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Tax",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AdvanceTax(Document):
pass

View File

@@ -1,190 +0,0 @@
{
"actions": [],
"creation": "2020-09-12 22:26:19.594367",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"add_deduct_tax",
"charge_type",
"row_id",
"account_head",
"col_break_1",
"description",
"included_in_paid_amount",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"section_break_8",
"rate",
"section_break_9",
"currency",
"tax_amount",
"total",
"allocated_amount",
"column_break_13",
"base_tax_amount",
"base_total"
],
"fields": [
{
"columns": 2,
"fieldname": "charge_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"oldfieldname": "charge_type",
"oldfieldtype": "Select",
"options": "\nActual\nOn Paid Amount\nOn Previous Row Amount\nOn Previous Row Total",
"reqd": 1
},
{
"depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1",
"fieldname": "row_id",
"fieldtype": "Data",
"label": "Reference Row #",
"oldfieldname": "row_id",
"oldfieldtype": "Data"
},
{
"columns": 2,
"fieldname": "account_head",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Account Head",
"oldfieldname": "account_head",
"oldfieldtype": "Link",
"options": "Account",
"reqd": 1,
"search_index": 1
},
{
"fieldname": "col_break_1",
"fieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description",
"oldfieldname": "description",
"oldfieldtype": "Small Text",
"print_width": "300px",
"reqd": 1,
"width": "300px"
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"default": ":Company",
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"oldfieldname": "cost_center_other_charges",
"oldfieldtype": "Link",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break"
},
{
"columns": 2,
"fieldname": "rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Rate",
"oldfieldname": "rate",
"oldfieldtype": "Currency"
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break"
},
{
"columns": 2,
"fieldname": "tax_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "currency"
},
{
"columns": 2,
"fieldname": "total",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Total",
"options": "currency",
"read_only": 1
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
"fieldname": "base_tax_amount",
"fieldtype": "Currency",
"label": "Amount (Company Currency)",
"oldfieldname": "tax_amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "base_total",
"fieldtype": "Currency",
"label": "Total (Company Currency)",
"oldfieldname": "total",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "add_deduct_tax",
"fieldtype": "Select",
"label": "Add Or Deduct",
"options": "Add\nDeduct",
"reqd": 1
},
{
"default": "0",
"fieldname": "included_in_paid_amount",
"fieldtype": "Check",
"label": "Considered In Paid Amount"
},
{
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"label": "Allocated Amount",
"options": "currency"
},
{
"fetch_from": "account_head.account_currency",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-11-25 11:10:10.945027",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Taxes and Charges",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "ASC"
}

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