Compare commits

...

392 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
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
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
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
Shivam Mishra
a791170f29 Merge pull request #19475 from 0Pranav/scheduling-ui-rewrite
feat: Appointment Schedulling
2019-11-26 13:43:14 +05:30
rohitwaghchaure
b5991e9264 Merge pull request #19683 from rohitwaghchaure/bom_ux_fixes_version_12
fix: BOM UX
2019-11-26 13:15:40 +05:30
0Pranav
fb1e87710b Tweaks to success redirect
- 5 seconds wait before redirect
- Edited description for URL in settings
2019-11-26 12:14:41 +05:30
0Pranav
8d2b0d800c Merge branch 'scheduling-ui-rewrite' of https://github.com/0Pranav/erpnext into scheduling-ui-rewrite 2019-11-26 10:55:53 +05:30
0Pranav
2515022377 add condition for zero appointment slots 2019-11-26 10:55:28 +05:30
Deepesh Garg
5558ee8597 Merge pull request #19591 from 0Pranav/FR-ISS-152101
fix: add status "expired" to doctype quotation
2019-11-26 09:08:30 +05:30
Deepesh Garg
31a4fa5dd3 Merge pull request #19606 from deepeshgarg007/shareholder_refactor
refactor: Share Transfer code cleanup and refactor
2019-11-26 07:54:57 +05:30
deepeshgarg007
53a66ee386 fix: Method name in hooks, test case code clean up 2019-11-25 21:58:15 +05:30
rohitwaghchaure
0881557c7d Merge pull request #19636 from rohitwaghchaure/not_able_to_submit_sales_invoice_e_invoice_issue
fix: e-invoice issue
2019-11-25 19:24:23 +05:30
Rohit Waghchaure
9326fb78f2 fix: BOM UX 2019-11-25 18:56:18 +05:30
Nabin Hait
ecc6b1917b Merge branch 'develop' of https://github.com/frappe/erpnext into develop 2019-11-25 18:13:58 +05:30
Nabin Hait
a7d168c05f fix: merge conflict 2019-11-25 18:12:56 +05:30
Pranav Nachnekar
fc5d8fcd9f Merge branch 'develop' into scheduling-ui-rewrite 2019-11-25 12:24:32 +00:00
0Pranav
b84e56ebb5 fix:travis tests 2019-11-25 17:32:02 +05:30
0Pranav
565d3efcdf fetch updated document in test_quotation 2019-11-25 17:11:12 +05:30
Pranav Nachnekar
d63ad3bb5f fix: add email group and newsletter links to CRM module view (#19679)
* fix: add email group and newsletter links to CRM module view

* chore: move email group to bottom
2019-11-25 16:50:47 +05:30
0Pranav
f9dec5201f fix:tests 2019-11-25 16:42:07 +05:30
0Pranav
cf3a2f6579 set transaction date to yesterday 2019-11-25 16:26:04 +05:30
0Pranav
032baeac5b don't submit quotation 2019-11-25 15:15:00 +05:30
0Pranav
754c43f6c3 fix set_expired_status method 2019-11-25 14:27:43 +05:30
0Pranav
e0c9f3c282 fix valid date 2019-11-25 14:14:51 +05:30
0Pranav
3ec5eabaf6 formatting 2019-11-25 14:11:31 +05:30
0Pranav
f2752bf38c fix: tests for python2 2019-11-25 14:09:49 +05:30
0Pranav
a9ff7df2e6 add sql query to set valid_till 2019-11-25 12:55:27 +05:30
Nabin Hait
2fd6814cbf Merge branch 'marination-account_stock_sync_error' into develop 2019-11-25 12:46:45 +05:30
Nabin Hait
1ed1c4e6a4 fix: make journal entry to sync stock and account balance 2019-11-25 12:33:40 +05:30
Pranav Nachnekar
04e3a506e4 fix: Primary address not being fetched for customer (#19667)
* fix: priamry address not being fetched

* add doctype to filter for customer_primary_address

* remove get_customer_primary_address_method
2019-11-25 12:04:00 +05:30
0Pranav
a077795581 fix tests 2019-11-25 12:01:00 +05:30
0Pranav
eefc492ff4 call commit after sql query for schedular job 2019-11-25 10:51:27 +05:30
Saqib
c9203a1bee fix: asset movement ux fixes (#19637) 2019-11-22 16:35:15 +05:30
Saqib
1919af2ff1 Fixed Asset Refactor Review fixes (#19666)
* fix: fixed asset item creation ux fixes

* fix: auto creation of asset ux fixes

* fix: [LCV] incorrect condition when checking assets linked with PR

* fix: bulk update assets

* refac: remove company level cwip enabling
* cwip can be enabled only on category level

* fix: #19649
2019-11-22 16:32:34 +05:30
Nabin Hait
aff4a67954 Merge branch 'account_stock_sync_error' of https://github.com/marination/erpnext into marination-account_stock_sync_error 2019-11-22 15:14:32 +05:30
Marica
b5c296da9e fix: Validation Error message on Prepared Report. (#19639)
Give the user the reason why he has to use filters.
2019-11-22 14:38:58 +05:30
Marica
7ca472780b fix: Get Current Stock button not working in Purchase Receipt (#19645)
- Field visible in grid view as well for better feedback
2019-11-22 14:37:38 +05:30
Anurag Mishra
763660b2e4 fix: set allocated amount in employee advance as per total amount (#19626) 2019-11-22 13:32:25 +05:30
0Pranav
6c368e2dfb submit quotation in test 2019-11-22 13:22:12 +05:30
0Pranav
b3354198f1 Fix sql query in set_expired_status 2019-11-22 12:38:43 +05:30
Mangesh-Khairnar
b10526dd86 fix: consider taxes in the grand total (#19631) 2019-11-22 12:28:33 +05:30
Saqib
290253fdd0 fix: last purchase rate greater than selling price (#19617) 2019-11-22 12:12:29 +05:30
Deepesh Garg
24cde55e28 fix: Patch for updating price or product discount field (#19642)
* fix: Patch for updating price or product discount field

* fix: Update pactch

* Update update_price_or_product_discount.py
2019-11-22 11:57:42 +05:30
Ben Knowles
49cd19d917 fix: update syntax error in company.js (#19661) 2019-11-22 11:36:02 +05:30
Nabin Hait
046137caa2 fix: Multiple fixes related to landed cost accounting (#19657) 2019-11-22 11:34:50 +05:30
Mangesh-Khairnar
1a92eb14ed fix: Mark attendance from employee attendance tool (#19627) 2019-11-21 17:58:18 +05:30
0Pranav
f5112905dc import make_item method in tests 2019-11-21 13:19:44 +05:30
Rohit Waghchaure
c4e6c42950 fix: e-invoice issue 2019-11-21 11:20:49 +05:30
Deepesh Garg
6a743be1de Merge pull request #19625 from ruchamahabal/fix_cash_entry
fix(minor): default Cash Entry account not getting fetched in Journal Entry
2019-11-20 16:15:55 +05:30
0Pranav
4a28144941 add tests 2019-11-20 15:53:19 +05:30
0Pranav
5717a265b7 remove: unused imports 2019-11-20 15:31:13 +05:30
0Pranav
ae90ea9547 fix:travis errors 2019-11-20 15:24:33 +05:30
0Pranav
682956543e fix travis 2019-11-20 11:45:14 +05:30
Rucha Mahabal
45e9dd9c51 fix(Journal Entry): default Cash Entry account not getting fetched 2019-11-20 11:40:38 +05:30
0Pranav
fe2147a496 fix travis 2019-11-20 11:37:49 +05:30
0Pranav
dbde140e46 fix: save method of Appointment Booking Setting 2019-11-20 10:30:41 +05:30
0Pranav
c8e66a0f71 Infer number_of_agents from agent_list in apppointment booking settings 2019-11-20 10:27:59 +05:30
rohitwaghchaure
26c46282e8 Merge pull request #19618 from nextchamp-saqib/pos_fix
fix: stock qty not displayed in pos
2019-11-19 19:41:25 +05:30
rohitwaghchaure
95270ad14b Merge pull request #19619 from rohitwaghchaure/not_able_to_select_item_in_the_sales_order
fix: not able to select item in sales order
2019-11-19 19:40:11 +05:30
rohitwaghchaure
616254d2a0 Merge pull request #19622 from rohitwaghchaure/code_cleanup_sales_invoice_credit_note
fix: code cleanup
2019-11-19 19:39:18 +05:30
Rohit Waghchaure
248585b5a1 fix: code cleanup 2019-11-19 19:21:27 +05:30
Rohit Waghchaure
c42312ea12 fix: not able to select item in sales order 2019-11-19 19:06:51 +05:30
rohitwaghchaure
a85ddf2fb4 fix: performance issue of sales invoice while save/submit (#19598)
* fix: performace issue of sales invoice while save/submit

* Cached price list data, item group child data, added indexing for blanket order
2019-11-19 18:47:48 +05:30
rohitwaghchaure
238521c2bd Merge pull request #19501 from Mangesh-Khairnar/multiple-pos-profile
feat: multiple company pos profile
2019-11-19 18:39:06 +05:30
thefalconx33
353f73a153 fix: stock qty not displayed in pos 2019-11-19 18:37:21 +05:30
Mangesh-Khairnar
c436d93303 fix: reset pos profile when default doesn't exists 2019-11-19 18:21:53 +05:30
Pranav Nachnekar
e13b769813 use nowdate instead of date.today()
Co-Authored-By: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
2019-11-19 12:04:30 +00:00
Joseph Marie Alba
2578d49b84 Correct bug in abbr cause by missing " " separator (#19605)
Only 1 letter ABBR is generated after typing in a COMPANY NAME separated by spaces. This is due to missing " " value in split method.

For example: 

Company Name:    ABC Multiple Industries
Generates Abbr:     A
Correct Abbr should be:  AMI

This is caused by mission " " in split method.
2019-11-19 14:54:09 +05:30
Shivam Mishra
776ff2f75d fix: query for item group listing (#19604) 2019-11-19 14:51:25 +05:30
Mangesh-Khairnar
f3ecfd8e58 fix: fetch leave approvers from both department and employee master (#19611)
* fix: fetch leave approvers from both department and employee master

* fix: creaate a set of approvers
2019-11-19 14:50:05 +05:30
0Pranav
539ea2cefb Rename doctype Appointment Booking Slots 2019-11-19 10:59:24 +05:30
0Pranav
b9460ed22c switched ORM methods for single SQL query 2019-11-19 10:46:07 +05:30
Deepesh Garg
ee69f707a9 Merge pull request #19600 from rohitwaghchaure/make_email_queue_issue_in_pos
fix: 'NoneType' object has no attribute 'replace' in POS
2019-11-19 09:47:55 +05:30
deepeshgarg007
0debcf9f2f fix: Share existing condition logic 2019-11-18 22:12:29 +05:30
deepeshgarg007
9c1c4ef3dd refactor: Share transfer cancellation and code cleanup 2019-11-18 17:52:19 +05:30
rohitwaghchaure
8131f7657e Merge pull request #19601 from rohitwaghchaure/fix_set_query_not_working_for_department_field
fix: not able to select department in instructor form
2019-11-18 16:40:42 +05:30
marination
6ef057a2a3 fix: Prefilled JV via Account Balance and Stock Value mismatch error message
- Make JV button will route to Journal Entry and add rows in child table
2019-11-18 15:55:32 +05:30
Rohit Waghchaure
39eeac265b fix: not able to select department in instructor form 2019-11-18 15:20:15 +05:30
Rohit Waghchaure
466702200f fix: 'NoneType' object has no attribute 'replace' in POS 2019-11-18 14:57:55 +05:30
Mangesh-Khairnar
f69b9a8c47 fix: fetch default pos profile user for the company 2019-11-15 16:54:26 +05:30
0Pranav
53b65ab8ed Add status expired to doctype quotation 2019-11-15 16:42:32 +05:30
0Pranav
18fda5a571 add appointment list to module page 2019-11-15 11:58:21 +05:30
deepeshgarg007
57bd1308eb fix: Validation messages code cleanup 2019-11-15 08:25:48 +05:30
0Pranav
511780a4d4 feat: configurable redirect on success 2019-11-14 12:47:08 +05:30
0Pranav
793ba8fc06 pretty timezone names 2019-11-14 11:25:49 +05:30
0Pranav
4006eb5277 Merge branch 'scheduling-ui-rewrite' of https://github.com/0Pranav/erpnext into scheduling-ui-rewrite 2019-11-13 17:12:24 +05:30
Pranav Nachnekar
ccd9b38463 Merge branch 'develop' into scheduling-ui-rewrite 2019-11-13 11:41:41 +00:00
0Pranav
c31808f5b2 fix margins 2019-11-13 16:47:51 +05:30
0Pranav
6e6954cab8 timezone aware datetime 2019-11-13 16:00:59 +05:30
0Pranav
b1e9fb9e14 fix: buttons on page of appointment scheduling 2019-11-13 15:32:56 +05:30
0Pranav
67f191df4e padding fixes for timeslot divs 2019-11-13 14:14:10 +05:30
0Pranav
c72e1f812d adjust padding for appointment booking 2019-11-13 12:59:05 +05:30
0Pranav
a92f060740 multiple fixes in index.js 2019-11-13 12:13:42 +05:30
0Pranav
f25e2a29f7 fix:formatting 2019-11-13 12:01:36 +05:30
0Pranav
cce000a6d0 remove: commented code 2019-11-13 11:48:37 +05:30
0Pranav
db64c69dac fix: reference before assignement error 2019-11-13 11:12:38 +05:30
0Pranav
f75ea952e3 Added expired status to quotation 2019-11-13 11:03:53 +05:30
deepeshgarg007
929676fceb fix: Validation logic code cleanup 2019-11-12 12:00:30 +05:30
0Pranav
83100c9c84 Add comemnts for tz conversions 2019-11-07 13:37:11 +05:30
0Pranav
0671ea8137 use frappe.Redirect instead of DoesNotExistError 2019-11-07 13:31:56 +05:30
0Pranav
76b20a5fa4 crack some one liners 2019-11-07 13:24:59 +05:30
0Pranav
151853b887 remove unneccessary imports 2019-11-07 12:55:43 +05:30
0Pranav
51208b3f0b fix:formatting 2019-11-07 12:54:48 +05:30
0Pranav
75db6f7073 convert indentation to tabs 2019-11-07 12:47:00 +05:30
0Pranav
fce8f36bb2 don't change lead if assigned 2019-11-07 12:37:28 +05:30
0Pranav
6f1d2eeffd changes to suggestions made by shivam 2019-11-06 11:57:37 +05:30
0Pranav
d1ee962d4b seperate function for sending confirmation 2019-11-05 14:53:36 +05:30
Pranav Nachnekar
4d3dc87a1a Apply suggestions from code review
Co-Authored-By: Shivam Mishra <scmmishra@users.noreply.github.com>
2019-11-05 04:32:06 +00:00
Pranav Nachnekar
af18b2cdc5 Merge branch 'develop' into scheduling-ui-rewrite 2019-11-04 04:38:59 +00:00
Mangesh-Khairnar
e3bc213262 feat: multiple company pos profile 2019-11-01 22:35:08 +05:30
0Pranav
e573bd9074 remove unnecessary variable 2019-11-01 12:47:11 +05:30
0Pranav
53ec8c6322 Merge branch 'develop' of https://github.com/0Pranav/erpnext into scheduling-ui-rewrite 2019-11-01 12:40:08 +05:30
0Pranav
97f6576213 prettify confirmation email 2019-11-01 12:36:06 +05:30
0Pranav
54f33f4e5d move utility functions 2019-11-01 12:14:21 +05:30
0Pranav
3609872760 rename function 2019-11-01 12:06:42 +05:30
0Pranav
6de68c8671 avoid repetition on get_form date 2019-11-01 09:51:32 +05:30
0Pranav
957c9f5ff0 fix:comments 2019-11-01 09:36:45 +05:30
0Pranav
4701bc8bfc Add ignore permissions for opportunity 2019-11-01 09:36:29 +05:30
0Pranav
e494144c96 merge settings fetch, add helpers 2019-10-31 15:38:39 +05:30
0Pranav
60093d98b0 auto assign before creating event 2019-10-31 15:37:57 +05:30
0Pranav
d1c530c564 fix: merge settings into one call 2019-10-31 15:36:33 +05:30
0Pranav
ad013264eb fix:margins 2019-10-15 16:45:37 +05:30
0Pranav
7c27436d21 fix:visibilty for forms 2019-10-15 16:45:24 +05:30
0Pranav
2f9ef85614 fix:typo 2019-10-15 16:44:28 +05:30
0Pranav
3d73a4f944 fix:readability for user 2019-10-15 16:43:40 +05:30
0Pranav
29c7d5fc63 fix:margins 2019-10-15 16:43:18 +05:30
Pranav Nachanekar
aa918e8528 moved validations to sepeate functions 2019-10-09 15:49:48 +05:30
Pranav Nachanekar
2c99594688 remove: styles for non existant radio 2019-10-09 15:22:57 +05:30
Pranav Nachanekar
9ccc43980b Merge branch 'develop' of https://github.com/0Pranav/erpnext into develop 2019-10-09 14:17:37 +05:30
Pranav Nachnekar
50e66d81de fix: use get_single_value
Co-Authored-By: Shivam Mishra <scmmishra@users.noreply.github.com>
2019-10-09 08:43:18 +00:00
Pranav Nachanekar
604febb398 fix: set_verified method contained js 2019-10-09 14:09:47 +05:30
Pranav Nachanekar
e434e8e2e2 fix: formatting 2019-10-09 14:08:01 +05:30
Pranav Nachnekar
96930e25f3 fix: readability
Co-Authored-By: Shivam Mishra <scmmishra@users.noreply.github.com>
2019-10-09 08:31:37 +00:00
Pranav Nachnekar
5e4ec85574 remove:unnecessary translation
Co-Authored-By: Shivam Mishra <scmmishra@users.noreply.github.com>
2019-10-09 08:23:54 +00:00
Pranav Nachnekar
1f1b7eb7d5 Merge branch 'develop' into develop 2019-10-07 06:12:45 +00:00
Pranav Nachanekar
e18388ade3 fix:add exception for no opportunity 2019-10-04 16:32:32 +05:30
Pranav Nachanekar
911e034d1c fix: syntax error 2019-10-04 15:50:02 +05:30
Pranav Nachanekar
0082b78075 fix:incosistent tabs and spaces 2019-10-04 15:36:36 +05:30
Pranav Nachanekar
bfe18d6085 feat:assign appointments from opportunity 2019-10-04 14:41:54 +05:30
Pranav Nachanekar
c1bc0f9dfb fix: added sections for settings 2019-10-04 11:36:53 +05:30
Pranav Nachanekar
25148d0de5 fix:readability 2019-10-04 11:32:39 +05:30
Pranav Nachanekar
9e36a9ee04 fix: move enable check to serverside 2019-10-04 11:28:29 +05:30
Pranav Nachanekar
faf39ecef4 fix:removed print statements 2019-10-04 11:12:50 +05:30
Pranav Nachanekar
22189ec9e8 remove unnecessary doctype 'Timezone' 2019-10-04 11:07:04 +05:30
Pranav Nachanekar
a1d39cab21 fix: travis 2019-10-03 18:26:02 +05:30
Pranav Nachanekar
d40c020e0e fix:variable names 2019-10-03 17:43:31 +05:30
Pranav Nachanekar
bec88bc52a fix: exists return tuple not string 2019-10-03 16:58:38 +05:30
Pranav Nachanekar
afe52e8e09 feat: add check for toggling the route 2019-10-03 16:35:08 +05:30
Pranav Nachanekar
72aac09d62 fix:remove tearDown from test 2019-10-03 16:09:50 +05:30
Pranav Nachanekar
43331564b4 fix:class variable in tests 2019-10-03 15:29:09 +05:30
Pranav Nachanekar
42cf5f279f fix:added class variables to test 2019-10-03 14:26:21 +05:30
Pranav Nachanekar
8640a01f85 remove duplicate day validation 2019-10-03 13:32:57 +05:30
Pranav Nachanekar
1dccc039b7 fix:add tear down to tests 2019-10-03 13:32:46 +05:30
Pranav Nachanekar
59c543570a feat: made timeslots into flex 2019-10-03 13:18:13 +05:30
Pranav Nachanekar
76cbb9132f fix: more test errors 2019-10-03 12:50:55 +05:30
Pranav Nachanekar
4856645b6d fix:styling for time-slot 2019-10-03 12:45:42 +05:30
Pranav Nachanekar
c5420bb453 fix: remove validation for repeated days 2019-10-03 12:06:43 +05:30
Pranav Nachanekar
93670fedda timezone manipulation using pytz 2019-10-03 11:58:02 +05:30
Pranav Nachanekar
1dcedb5054 fix: empty leads and appointment in test 2019-10-03 11:56:54 +05:30
Pranav Nachanekar
c6da5fb38e fix:guess timezone using moment 2019-10-03 11:56:23 +05:30
Pranav Nachanekar
2ea9b3e6f2 fix:test appointments 2019-09-30 15:35:38 +05:30
Pranav Nachanekar
14a3e64c80 Merge branch 'develop' of https://github.com/0Pranav/erpnext into develop 2019-09-30 13:58:48 +05:30
Pranav Nachanekar
7f4bc64d22 fix:appointment tests exist check 2019-09-30 13:58:42 +05:30
Pranav Nachanekar
250bae2603 fix:appointment tests exist check 2019-09-30 12:40:25 +05:30
Pranav Nachanekar
fd46bf2616 fix codacy 2019-09-25 16:01:48 +05:30
Pranav Nachanekar
774167192a Merge branch 'develop' of https://github.com/0Pranav/erpnext into develop 2019-09-25 13:12:32 +05:30
Pranav Nachanekar
291e161793 Added permissions for sales user 2019-09-25 13:11:04 +05:30
Pranav Nachanekar
9f86022c2b fix: Error in test setUp 2019-09-24 16:07:41 +05:30
Pranav Nachanekar
d45c12b382 Formatting 2019-09-24 16:07:02 +05:30
Pranav Nachanekar
c9cf5aebea Changed required values, add clientside validation 2019-09-24 12:08:37 +05:30
Nabin Hait
7ab861fe95 Merge branch 'develop' into develop 2019-09-24 11:50:38 +05:30
Pranav Nachanekar
558d44e519 Removed auto-assignment for unverified appointments 2019-09-24 11:33:57 +05:30
Pranav Nachanekar
8393ebbbca Fixed missing permission in update 2019-09-23 17:14:31 +05:30
Pranav Nachanekar
8b744b2d03 Added request verification and url encoding 2019-09-23 15:55:35 +05:30
Pranav Nachanekar
a35e34b5f0 FIxed typos and create_lead method 2019-09-23 14:38:22 +05:30
Pranav Nachanekar
83dee07420 Merge branch 'develop' of https://github.com/0Pranav/erpnext into develop 2019-09-23 14:25:55 +05:30
Pranav Nachanekar
3eccb84eaa Fixed update method 2019-09-23 14:25:42 +05:30
Pranav Nachanekar
e40b100110 Fixed update method 2019-09-23 14:23:04 +05:30
Pranav Nachanekar
b6b27d9256 Corrected moving to doctype for email 2019-09-23 14:16:13 +05:30
Pranav Nachanekar
7b7962d28c Added test cases 2019-09-23 13:05:18 +05:30
Pranav Nachanekar
77744766dd Merge branch 'develop' of https://github.com/0Pranav/erpnext into develop 2019-09-23 11:52:37 +05:30
Pranav Nachanekar
dcfc849946 Moved email to appoitnmetn doctype 2019-09-23 11:52:26 +05:30
Pranav Nachanekar
d9ab09ab2b Moved email to appoitnmetn doctype 2019-09-23 11:28:17 +05:30
Pranav Nachanekar
f8cc86bfed Moved email from class variable to doctype
Formatting
Made methods which link other doctypes idempotent
2019-09-23 11:28:05 +05:30
Pranav Nachanekar
0800031c0d Addee email to appointment doctyoe
and asthetic changes
2019-09-23 11:26:46 +05:30
Pranav Nachanekar
6b0fea16b6 Added buttons to linked docs 2019-09-23 11:26:18 +05:30
Pranav Nachanekar
9c0f462336 Fixed Syntax errors 2019-09-20 10:51:56 +05:30
Pranav Nachanekar
07bb46e3fb Merge branch 'develop' of https://github.com/0Pranav/erpnext into develop 2019-09-20 10:47:59 +05:30
Pranav Nachanekar
aa7d0c0163 Merge branch 'develop' of https://github.com/0Pranav/erpnext into develop 2019-09-20 10:47:18 +05:30
Pranav Nachanekar
a9f5a697bb Merge branch 'develop' of https://github.com/0Pranav/erpnext into develop 2019-09-20 10:45:48 +05:30
Pranav Nachanekar
73420e462f Added email 2019-09-20 10:44:52 +05:30
Pranav Nachanekar
fa4a2a53e8 Added email 2019-09-20 10:41:59 +05:30
Pranav Nachanekar
df1a5a9633 Added flow for verifying emails 2019-09-20 10:08:48 +05:30
Pranav Nachanekar
5324234bd0 Removed required lead 2019-09-20 10:08:26 +05:30
Pranav Nachanekar
ca2509423a Added permissions for HR manager 2019-09-19 12:36:51 +05:30
Pranav Nachanekar
4109f88c04 Linked send_reminder in calendar event
to Appointment Booking Settings
2019-09-19 12:08:10 +05:30
Pranav Nachanekar
5bf52ebed6 limit assigment load to appointment day 2019-09-19 11:47:54 +05:30
Pranav Nachanekar
ba99945359 Prevent booking of appointments for past times 2019-09-19 11:21:05 +05:30
Pranav Nachanekar
ec1dae023c styling 2019-09-18 16:13:29 +05:30
Pranav Nachanekar
7d476a3e35 Moved lead assignment to the controller 2019-09-18 15:33:31 +05:30
Pranav Nachanekar
81449ece54 fix:Linking lead 2019-09-18 14:33:40 +05:30
Pranav Nachanekar
7323bfdad7 Styling and bug fixes 2019-09-18 14:33:10 +05:30
Pranav Nachanekar
3c8bea65ef Merged with local 2019-09-18 10:39:11 +05:30
Pranav Nachnekar
57c311f8e9 Merge branch 'develop' into develop 2019-09-18 10:33:39 +05:30
Pranav Nachanekar
91a564989f Styling and PR review changes 2019-09-17 16:58:41 +05:30
Pranav Nachnekar
33daf281bd Merge branch 'develop' into develop 2019-09-17 14:14:16 +05:30
Pranav Nachnekar
a8752db012 Typo and styling fixes
Co-Authored-By: Shivam Mishra <scmmishra@users.noreply.github.com>
2019-09-16 20:02:20 +05:30
Pranav Nachanekar
018f0d3bbd Fixed issue: agents weren't looked up in settings 2019-09-13 16:52:06 +05:30
Pranav Nachanekar
1cd762e9d0 Added ajuto assignment 2019-09-13 15:56:54 +05:30
Pranav Nachanekar
d88f850d0f removed debugger 2019-09-13 15:56:47 +05:30
Pranav Nachanekar
cf045d86b0 fixed typo 2019-09-13 15:55:54 +05:30
Pranav Nachanekar
a3b8c77af1 Fixed leads 2019-09-12 15:19:22 +05:30
Pranav Nachanekar
1564f1476c Added customer to calender event 2019-09-12 14:24:28 +05:30
Pranav Nachanekar
c9c8e19ec2 Merge branch 'develop' of https://github.com/0Pranav/erpnext into develop 2019-09-12 14:13:18 +05:30
Pranav Nachanekar
469247bf73 Change max date of datepicker
to number of days in future as specified by the settings
2019-09-12 11:15:42 +05:30
Pranav Nachanekar
a2dbd391b3 Add lead and calender event to appointments 2019-09-12 10:48:26 +05:30
Pranav Nachanekar
8051ca1859 Limit advance booking of appointments 2019-09-12 10:47:45 +05:30
Pranav Nachanekar
249cdd92e0 Added uniqueness check for offset 2019-09-11 14:59:25 +05:30
Pranav Nachanekar
e543fc483f Removed email reminders
As it will be handled by calender event in the future
2019-09-11 14:59:13 +05:30
Pranav Nachanekar
a322b159ab Added back button from details page 2019-09-11 14:25:26 +05:30
Pranav Nachanekar
0cc837eac5 Create event for the appointment
TODO: Add lead and employee to this
2019-09-11 14:12:30 +05:30
Pranav Nachnekar
4897897a3a Merge branch 'develop' into develop 2019-09-11 11:21:26 +05:30
Pranav Nachanekar
5038d6a6db Removed appointment tests
TODO: Write better tests after adding lead and calender event generation
2019-09-11 10:31:04 +05:30
Pranav Nachanekar
2d7370a525 Moved delete_appointment 2019-09-10 16:46:17 +05:30
Pranav Nachanekar
c4950a0281 Added doctype
availabitlity of slots added
2019-09-10 15:10:51 +05:30
Pranav Nachanekar
6f486f3719 Addded status to appointment creation 2019-09-10 13:12:28 +05:30
Pranav Nachanekar
5c008ef023 UI Fixes
Only 8 time slots will appear in a row
Date is more readable on the contact details page
2019-09-10 13:12:07 +05:30
Pranav Nachanekar
5c211d8abf fixed codacy 2019-09-10 11:52:55 +05:30
Pranav Nachnekar
4ac25f28a3 Merge branch 'develop' into develop 2019-09-10 06:01:33 +00:00
Pranav Nachanekar
06facea895 added doctype 2019-09-09 17:09:03 +05:30
Pranav Nachanekar
110f4ea0c9 Formatting 2019-09-09 17:04:25 +05:30
Pranav Nachanekar
db21f86b26 Removed unneccessary doctype 2019-09-09 17:01:40 +05:30
Pranav Nachanekar
20c7c290fa Formatting 2019-09-09 16:36:01 +05:30
Pranav Nachanekar
5945144c08 Added tests 2019-09-09 16:35:48 +05:30
Pranav Nachanekar
10711dd09d Refactor UI 2019-09-09 15:41:20 +05:30
Pranav Nachanekar
63dbacd7c0 Disabled caching 2019-09-09 15:19:57 +05:30
Pranav Nachanekar
48e43e2421 build fix 2019-09-09 14:43:55 +05:30
Pranav Nachanekar
217aadba7e Better autoname 2019-09-09 14:43:41 +05:30
Nabin Hait
eb2ab3bf76 Merge branch 'develop' into develop 2019-09-05 14:59:16 +05:30
pranav nachnekar
d272fd42c9 changed Autoname 2019-09-03 14:16:56 +05:30
pranav nachnekar
c5b2a58669 Added submit fucntionality 2019-09-03 14:16:47 +05:30
pranav nachnekar
a3d04cdbca A 2019-09-03 13:17:35 +05:30
pranav nachnekar
2791054327 Converted to an SPA 2019-09-03 12:04:52 +05:30
pranav nachnekar
eb4fa966b0 Added validation for timezones 2019-09-03 12:04:23 +05:30
pranav nachnekar
ad6151b690 Added guest permissions 2019-09-03 12:04:13 +05:30
pranav nachnekar
64095084e7 Removed end time in favor of appointment duration 2019-09-03 12:03:14 +05:30
pranav nachnekar
828fea6d66 formatting and date validation 2019-08-30 10:49:57 +05:30
pranav nachnekar
2cd994977b Better date validation 2019-08-30 10:49:43 +05:30
pranav nachnekar
17906d5599 Added polyfill for datepicker
for Safari and IE  support
2019-08-30 10:49:07 +05:30
pranav nachnekar
25052a0ab7 Merge branch 'develop' of https://github.com/frappe/erpnext into develop 2019-08-29 16:56:54 +05:30
pranav nachnekar
dbd72ea89d Added time generation 2019-08-29 16:56:19 +05:30
pranav nachnekar
adba6c833d Merge branch 'develop' of https://github.com/frappe/erpnext into develop 2019-08-28 17:00:27 +05:30
pranav nachnekar
7b9e30914f Added doctypes and portal pages 2019-08-28 16:57:37 +05:30
167 changed files with 5631 additions and 9818 deletions

View File

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

View File

@@ -109,12 +109,13 @@ class Account(NestedSet):
if not descendants: return
parent_acc_name_map = {}
parent_acc_name = frappe.db.get_value('Account', self.parent_account, "account_name")
parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
["account_name", "account_number"])
for d in frappe.db.get_values('Account',
{"company": ["in", descendants], "account_name": parent_acc_name},
{ "company": ["in", descendants], "account_name": parent_acc_name,
"account_number": parent_acc_number },
["company", "name"], as_dict=True):
parent_acc_name_map[d["company"]] = d["name"]
if not parent_acc_name_map: return
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)

View File

@@ -15,8 +15,8 @@ def upload_bank_statement():
with open(frappe.uploaded_file, "rb") as upfile:
fcontent = upfile.read()
else:
from frappe.utils.file_manager import get_uploaded_content
fname, fcontent = get_uploaded_content()
fcontent = frappe.local.uploaded_file
fname = frappe.local.uploaded_filename
if frappe.safe_encode(fname).lower().endswith("csv".encode('utf-8')):
from frappe.utils.csvutils import read_csv_content

View File

@@ -398,7 +398,7 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) {
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
args: {
"account_type": (doc.voucher_type=="Bank Entry" ?
"Bank" : (doc.voucher_type=="Cash" ? "Cash" : null)),
"Bank" : (doc.voucher_type=="Cash Entry" ? "Cash" : null)),
"company": doc.company
},
callback: function(r) {

View File

@@ -931,9 +931,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt in ("Expense Claim"):
grand_total = doc.total_sanctioned_amount
outstanding_amount = doc.total_sanctioned_amount \
- doc.total_amount_reimbursed - flt(doc.total_advance_amount)
grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
outstanding_amount = doc.grand_total \
- doc.total_amount_reimbursed
elif dt == "Employee Advance":
grand_total = doc.advance_amount
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)

View File

@@ -350,13 +350,13 @@ def get_amount(ref_doc):
if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
if dt in ["Sales Invoice", "Purchase Invoice"]:
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if ref_doc.party_account_currency == ref_doc.currency:
grand_total = flt(ref_doc.outstanding_amount)
else:
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
if dt == "Fees":
elif dt == "Fees":
grand_total = ref_doc.outstanding_amount
if grand_total > 0 :

View File

@@ -389,8 +389,7 @@
"fieldname": "rate_or_discount",
"fieldtype": "Select",
"label": "Rate or Discount",
"options": "\nRate\nDiscount Percentage\nDiscount Amount",
"reqd": 1
"options": "\nRate\nDiscount Percentage\nDiscount Amount"
},
{
"default": "Grand Total",
@@ -439,19 +438,20 @@
},
{
"default": "0",
"depends_on": "eval:!doc.mixed_conditions",
"depends_on": "eval:!doc.mixed_conditions && doc.apply_on != 'Transaction'",
"fieldname": "same_item",
"fieldtype": "Check",
"label": "Same Item"
},
{
"depends_on": "eval:!doc.same_item || doc.mixed_conditions",
"depends_on": "eval:(!doc.same_item || doc.apply_on == 'Transaction') || doc.mixed_conditions",
"fieldname": "free_item",
"fieldtype": "Link",
"label": "Free Item",
"options": "Item"
},
{
"default": "0",
"fieldname": "free_qty",
"fieldtype": "Float",
"label": "Qty"
@@ -554,7 +554,7 @@
],
"icon": "fa fa-gift",
"idx": 1,
"modified": "2019-10-15 12:39:40.399792",
"modified": "2019-12-18 17:29:22.957077",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@@ -48,6 +48,9 @@ class PricingRule(Document):
if tocheck and not self.get(tocheck):
throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError)
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
def validate_applicable_for_selling_or_buying(self):
if not self.selling and not self.buying:
throw(_("Atleast one of the Selling or Buying must be selected"))
@@ -181,8 +184,9 @@ def get_serial_no_for_item(args):
item_details.serial_no = get_serial_no(args)
return item_details
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rules
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules,
get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule)
if isinstance(doc, string_types):
doc = json.loads(doc)
@@ -209,6 +213,57 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
item_details, args.get('item_code'))
return item_details
update_args_for_pricing_rule(args)
pricing_rules = (get_applied_pricing_rules(args)
if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc))
if pricing_rules:
rules = []
for pricing_rule in pricing_rules:
if not pricing_rule: continue
if isinstance(pricing_rule, string_types):
pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule)
if pricing_rule.get('suggestion'): continue
item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0)
item_details.price_or_product_discount = pricing_rule.get("price_or_product_discount")
rules.append(get_pricing_rule_details(args, pricing_rule))
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
item_details.update({
'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items),
'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other)
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
})
if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
return item_details
if not pricing_rule.validate_applied_rule:
if pricing_rule.price_or_product_discount == "Price":
apply_price_discount_rule(pricing_rule, item_details, args)
else:
get_product_discount_rule(pricing_rule, item_details, doc)
item_details.has_pricing_rule = 1
item_details.pricing_rules = ','.join([d.pricing_rule for d in rules])
if not doc: return item_details
elif args.get("pricing_rules"):
item_details = remove_pricing_rule_for_item(args.get("pricing_rules"),
item_details, args.get('item_code'))
return item_details
def update_args_for_pricing_rule(args):
if not (args.item_group and args.brand):
try:
args.item_group, args.brand = frappe.get_cached_value("Item", args.item_code, ["item_group", "brand"])
@@ -235,56 +290,16 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
args.supplier_group = frappe.get_cached_value("Supplier", args.supplier, "supplier_group")
args.customer = args.customer_group = args.territory = None
pricing_rules = get_pricing_rules(args, doc)
if pricing_rules:
rules = []
for pricing_rule in pricing_rules:
if not pricing_rule or pricing_rule.get('suggestion'): continue
item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0)
rules.append(get_pricing_rule_details(args, pricing_rule))
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
continue
if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
return item_details
if (not pricing_rule.validate_applied_rule and
pricing_rule.price_or_product_discount == "Price"):
apply_price_discount_pricing_rule(pricing_rule, item_details, args)
item_details.has_pricing_rule = 1
# if discount is applied on the rate and not on price list rate
# if price_list_rate:
# set_discount_amount(price_list_rate, item_details)
item_details.pricing_rules = ','.join([d.pricing_rule for d in rules])
if not doc: return item_details
for rule in rules:
doc.append('pricing_rules', rule)
elif args.get("pricing_rules"):
item_details = remove_pricing_rule_for_item(args.get("pricing_rules"),
item_details, args.get('item_code'))
return item_details
def get_pricing_rule_details(args, pricing_rule):
return frappe._dict({
'pricing_rule': pricing_rule.name,
'rate_or_discount': pricing_rule.rate_or_discount,
'margin_type': pricing_rule.margin_type,
'item_code': pricing_rule.item_code or args.get("item_code"),
'item_code': args.get("item_code"),
'child_docname': args.get('child_docname')
})
def apply_price_discount_pricing_rule(pricing_rule, item_details, args):
def apply_price_discount_rule(pricing_rule, item_details, args):
item_details.pricing_rule_for = pricing_rule.rate_or_discount
if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency)
@@ -327,10 +342,10 @@ def set_discount_amount(rate, item_details):
item_details.rate = rate
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
from erpnext.accounts.doctype.pricing_rule.utils import get_apply_on_and_items
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items
for d in pricing_rules.split(','):
if not d or not frappe.db.exists("Pricing Rule", d): continue
pricing_rule = frappe.get_doc('Pricing Rule', d)
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
if pricing_rule.price_or_product_discount == 'Price':
if pricing_rule.rate_or_discount == 'Discount Percentage':
@@ -348,8 +363,9 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
else pricing_rule.get('free_item'))
if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"):
apply_on, items = get_apply_on_and_items(pricing_rule, item_details)
item_details.apply_on = apply_on
items = get_pricing_rule_items(pricing_rule)
item_details.apply_on = (frappe.scrub(pricing_rule.apply_rule_on_other)
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
item_details.applied_on_items = ','.join(items)
item_details.pricing_rules = ''

View File

@@ -7,7 +7,8 @@ from __future__ import unicode_literals
import frappe, copy, json
from frappe import throw, _
from six import string_types
from frappe.utils import flt, cint, get_datetime
from frappe.utils import flt, cint, get_datetime, get_link_to_form, today
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor
@@ -173,10 +174,11 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
if (field and pricing_rules[0].get('other_' + field) != args.get(field)): return
pr_doc = frappe.get_doc('Pricing Rule', pricing_rules[0].name)
pr_doc = frappe.get_cached_doc('Pricing Rule', pricing_rules[0].name)
if pricing_rules[0].mixed_conditions and doc:
stock_qty, amount = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args)
stock_qty, amount, items = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args)
pricing_rules[0].apply_rule_on_other_items = items
elif pricing_rules[0].is_cumulative:
items = [args.get(frappe.scrub(pr_doc.get('apply_on')))]
@@ -282,7 +284,7 @@ def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None):
status = True
# if user has created item price against the transaction UOM
if rule.get("uom") == args.get("uom"):
if args and rule.get("uom") == args.get("uom"):
conversion_factor = 1.0
if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor)
@@ -339,17 +341,19 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
sum_qty += data[0]
sum_amt += data[1]
return sum_qty, sum_amt
return sum_qty, sum_amt, items
def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules):
for d in get_pricing_rule_items(pr_doc):
for row in doc.items:
if d == row.get(frappe.scrub(pr_doc.apply_on)):
pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
row.get("amount"), pricing_rules, row)
items = get_pricing_rule_items(pr_doc)
if pricing_rules and pricing_rules[0]:
return pricing_rules
for row in doc.items:
if row.get(frappe.scrub(pr_doc.apply_rule_on_other)) in items:
pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
row.get("amount"), pricing_rules, row)
if pricing_rules and pricing_rules[0]:
pricing_rules[0].apply_rule_on_other_items = items
return pricing_rules
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
sum_qty, sum_amt = [0, 0]
@@ -397,38 +401,15 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
return [sum_qty, sum_amt]
def validate_pricing_rules(doc):
validate_pricing_rule_on_transactions(doc)
for d in doc.items:
validate_pricing_rule_on_items(doc, d)
doc.calculate_taxes_and_totals()
def validate_pricing_rule_on_items(doc, item_row, do_not_validate = False):
value = 0
for pricing_rule in get_applied_pricing_rules(doc, item_row):
pr_doc = frappe.get_doc('Pricing Rule', pricing_rule)
if pr_doc.get('apply_on') == 'Transaction': continue
if pr_doc.get('price_or_product_discount') == 'Product':
apply_pricing_rule_for_free_items(doc, pr_doc)
else:
for field in ['discount_percentage', 'discount_amount', 'rate']:
if not pr_doc.get(field): continue
value += pr_doc.get(field)
apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate)
def validate_pricing_rule_on_transactions(doc):
def apply_pricing_rule_on_transaction(doc):
conditions = "apply_on = 'Transaction'"
values = {}
conditions = get_other_conditions(conditions, values, doc)
pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule`
where {conditions} """.format(conditions = conditions), values, as_dict=1)
where {conditions} and `tabPricing Rule`.disable = 0
""".format(conditions = conditions), values, as_dict=1)
if pricing_rules:
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
@@ -440,98 +421,83 @@ def validate_pricing_rule_on_transactions(doc):
doc.set('apply_discount_on', d.apply_discount_on)
for field in ['additional_discount_percentage', 'discount_amount']:
if not d.get(field): continue
pr_field = ('discount_percentage'
if field == 'additional_discount_percentage' else field)
if not d.get(pr_field): continue
if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
frappe.msgprint(_("User has not applied rule on the invoice {0}")
.format(doc.name))
else:
doc.set(field, d.get(pr_field))
elif d.price_or_product_discount == 'Product':
apply_pricing_rule_for_free_items(doc, d)
def get_applied_pricing_rules(doc, item_row):
doc.calculate_taxes_and_totals()
elif d.price_or_product_discount == 'Product':
item_details = frappe._dict({'parenttype': doc.doctype})
get_product_discount_rule(d, item_details, doc)
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values()
def get_applied_pricing_rules(item_row):
return (item_row.get("pricing_rules").split(',')
if item_row.get("pricing_rules") else [])
def apply_pricing_rule_for_free_items(doc, pricing_rule):
if pricing_rule.get('free_item'):
def get_product_discount_rule(pricing_rule, item_details, doc=None):
free_item = (pricing_rule.free_item
if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code)
if not free_item:
frappe.throw(_("Free item not set in the pricing rule {0}")
.format(get_link_to_form("Pricing Rule", pricing_rule.name)))
item_details.free_item_data = {
'item_code': free_item,
'qty': pricing_rule.free_qty or 1,
'rate': pricing_rule.free_item_rate or 0,
'price_list_rate': pricing_rule.free_item_rate or 0,
'is_free_item': 1
}
item_data = frappe.get_cached_value('Item', free_item, ['item_name',
'description', 'stock_uom'], as_dict=1)
item_details.free_item_data.update(item_data)
item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item,
item_details.free_item_data['uom']).get("conversion_factor", 1)
if item_details.get("parenttype") == 'Purchase Order':
item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today()
if item_details.get("parenttype") == 'Sales Order':
item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
if pricing_rule_args.get('item_code'):
items = [d.item_code for d in doc.items
if d.item_code == (d.item_code
if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item]
if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item]
if not items:
doc.append('items', {
'item_code': pricing_rule.get('free_item'),
'qty': pricing_rule.get('free_qty'),
'uom': pricing_rule.get('free_item_uom'),
'rate': pricing_rule.get('free_item_rate'),
'is_free_item': 1
})
doc.set_missing_values()
def apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate=False):
apply_on, items = get_apply_on_and_items(pr_doc, item_row)
rule_applied = {}
for item in doc.get("items"):
if item.get(apply_on) in items:
if not item.pricing_rules:
item.pricing_rules = item_row.pricing_rules
for field in ['discount_percentage', 'discount_amount', 'rate']:
if not pr_doc.get(field): continue
key = (item.name, item.pricing_rules)
if not pr_doc.validate_applied_rule:
rule_applied[key] = 1
item.set(field, value)
elif item.get(field) < value:
if not do_not_validate and item.idx == item_row.idx:
rule_applied[key] = 0
frappe.msgprint(_("Row {0}: user has not applied rule <b>{1}</b> on the item <b>{2}</b>")
.format(item.idx, pr_doc.title, item.item_code))
if rule_applied and doc.get("pricing_rules"):
for d in doc.get("pricing_rules"):
key = (d.child_docname, d.pricing_rule)
if key in rule_applied:
d.rule_applied = 1
def get_apply_on_and_items(pr_doc, item_row):
# for mixed or other items conditions
apply_on = frappe.scrub(pr_doc.get('apply_on'))
items = (get_pricing_rule_items(pr_doc)
if pr_doc.mixed_conditions else [item_row.get(apply_on)])
if pr_doc.apply_rule_on_other:
apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
items = [pr_doc.get(apply_on)]
return apply_on, items
doc.append('items', pricing_rule_args)
def get_pricing_rule_items(pr_doc):
apply_on_data = []
apply_on = frappe.scrub(pr_doc.get('apply_on'))
pricing_rule_apply_on = apply_on_table.get(pr_doc.get('apply_on'))
return [item.get(apply_on) for item in pr_doc.get(pricing_rule_apply_on)] or []
for d in pr_doc.get(pricing_rule_apply_on):
if apply_on == 'item_group':
get_child_item_groups(d.get(apply_on))
else:
apply_on_data.append(d.get(apply_on))
@frappe.whitelist()
def validate_pricing_rule_for_different_cond(doc):
if isinstance(doc, string_types):
doc = json.loads(doc)
if pr_doc.apply_rule_on_other:
apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
apply_on_data.append(pr_doc.get(apply_on))
doc = frappe.get_doc(doc)
for d in doc.get("items"):
validate_pricing_rule_on_items(doc, d, True)
return doc
return list(set(apply_on_data))
def validate_coupon_code(coupon_name):
from frappe.utils import today,getdate

View File

@@ -248,7 +248,7 @@ class PurchaseInvoice(BuyingController):
def set_against_expense_account(self):
against_accounts = []
for item in self.get("items"):
if item.expense_account not in against_accounts:
if item.expense_account and (item.expense_account not in against_accounts):
against_accounts.append(item.expense_account)
self.against_expense_account = ",".join(against_accounts)
@@ -830,7 +830,11 @@ class PurchaseInvoice(BuyingController):
)
def make_gle_for_rounding_adjustment(self, gl_entries):
if self.rounding_adjustment:
# if rounding adjustment in small and conversion rate is also small then
# base_rounding_adjustment may become zero due to small precision
# eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2
# then base_rounding_adjustment becomes zero and error is thrown in GL Entry
if self.rounding_adjustment and self.base_rounding_adjustment:
round_off_account, round_off_cost_center = \
get_round_off_account_and_cost_center(self.company)

View File

@@ -0,0 +1,3 @@
{% include "erpnext/regional/india/taxes.js" %}
erpnext.setup_auto_gst_taxation('Purchase Invoice');

View File

@@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "hash",
"creation": "2013-05-22 12:43:10",
"doctype": "DocType",
@@ -507,7 +508,8 @@
"depends_on": "enable_deferred_expense",
"fieldname": "service_stop_date",
"fieldtype": "Date",
"label": "Service Stop Date"
"label": "Service Stop Date",
"no_copy": 1
},
{
"default": "0",
@@ -523,13 +525,15 @@
"depends_on": "enable_deferred_expense",
"fieldname": "service_start_date",
"fieldtype": "Date",
"label": "Service Start Date"
"label": "Service Start Date",
"no_copy": 1
},
{
"depends_on": "enable_deferred_expense",
"fieldname": "service_end_date",
"fieldtype": "Date",
"label": "Service End Date"
"label": "Service End Date",
"no_copy": 1
},
{
"fieldname": "reference",
@@ -766,7 +770,8 @@
],
"idx": 1,
"istable": 1,
"modified": "2019-11-21 16:27:52.043744",
"links": [],
"modified": "2019-12-04 12:23:17.046413",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -1,300 +1,108 @@
{
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
"beta": 0,
"creation": "2013-01-10 16:34:08",
"custom": 0,
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"allow_import": 1,
"allow_rename": 1,
"creation": "2013-01-10 16:34:08",
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
"doctype": "DocType",
"document_type": "Setup",
"field_order": [
"title",
"is_default",
"disabled",
"column_break4",
"company",
"tax_category",
"section_break6",
"taxes"
],
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Title",
"length": 0,
"no_copy": 1,
"oldfieldname": "title",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"no_copy": 1,
"oldfieldname": "title",
"oldfieldtype": "Data",
"reqd": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_default",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Default",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"default": "0",
"fieldname": "is_default",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Default"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Disabled",
"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,
"unique": 0
},
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Disabled"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"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,
"unique": 0
},
"fieldname": "column_break4",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 1,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"options": "Company",
"remember_last_selected_value": 1,
"reqd": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"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,
"unique": 0
},
"fieldname": "section_break6",
"fieldtype": "Section Break"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "taxes",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Purchase Taxes and Charges",
"length": 0,
"no_copy": 0,
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
"options": "Purchase Taxes and Charges",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"fieldname": "taxes",
"fieldtype": "Table",
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
"options": "Purchase Taxes and Charges"
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category"
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-money",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-11-07 05:18:44.095798",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges Template",
"owner": "wasim@webnotestech.com",
],
"icon": "fa fa-money",
"idx": 1,
"modified": "2019-11-25 13:05:26.220275",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges Template",
"owner": "wasim@webnotestech.com",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Purchase Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Purchase Manager"
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Purchase Master Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Purchase Master Manager",
"share": 1,
"write": 1
},
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Purchase User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
"read": 1,
"role": "Purchase User"
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_order": "DESC",
"track_seen": 0
],
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -357,14 +357,11 @@ def get_customer_wise_price_list():
def get_bin_data(pos_profile):
itemwise_bin_data = {}
cond = "1=1"
filters = { 'actual_qty': ['>', 0] }
if pos_profile.get('warehouse'):
cond = "warehouse = %(warehouse)s"
filters.update({ 'warehouse': pos_profile.get('warehouse') })
bin_data = frappe.db.sql(""" select item_code, warehouse, actual_qty from `tabBin`
where actual_qty > 0 and {cond}""".format(cond=cond), {
'warehouse': frappe.db.escape(pos_profile.get('warehouse'))
}, as_dict=1)
bin_data = frappe.db.get_all('Bin', fields = ['item_code', 'warehouse', 'actual_qty'], filters=filters)
for bins in bin_data:
if bins.item_code not in itemwise_bin_data:
@@ -550,11 +547,15 @@ def make_address(args, customer):
def make_email_queue(email_queue):
name_list = []
for key, data in iteritems(email_queue):
name = frappe.db.get_value('Sales Invoice', {'offline_pos_name': key}, 'name')
if not name: continue
data = json.loads(data)
sender = frappe.session.user
print_format = "POS Invoice" if not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')) else None
attachments = [frappe.attach_print('Sales Invoice', name, print_format=print_format)]
make(subject=data.get('subject'), content=data.get('content'), recipients=data.get('recipients'),

View File

@@ -1,3 +1,7 @@
{% include "erpnext/regional/india/taxes.js" %}
erpnext.setup_auto_gst_taxation('Sales Invoice');
frappe.ui.form.on("Sales Invoice", {
setup: function(frm) {
frm.set_query('transporter', function() {
@@ -34,5 +38,8 @@ frappe.ui.form.on("Sales Invoice", {
}
}, __("Make"));
}
}
},
});

View File

@@ -697,8 +697,8 @@ frappe.ui.form.on('Sales Invoice', {
if (frm.doc.company)
{
frappe.call({
method:"frappe.contacts.doctype.address.address.get_default_address",
args:{ doctype:'Company',name:frm.doc.company},
method:"erpnext.setup.doctype.company.company.get_default_company_address",
args:{name:frm.doc.company, existing_address: frm.doc.company_address},
callback: function(r){
if (r.message){
frm.set_value("company_address",r.message)

View File

@@ -953,7 +953,7 @@ class SalesInvoice(SellingController):
)
def make_gle_for_rounding_adjustment(self, gl_entries):
if flt(self.rounding_adjustment, self.precision("rounding_adjustment")):
if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment:
round_off_account, round_off_cost_center = \
get_round_off_account_and_cost_center(self.company)

View File

@@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "hash",
"creation": "2013-06-04 11:02:19",
"doctype": "DocType",
@@ -484,7 +485,8 @@
"depends_on": "enable_deferred_revenue",
"fieldname": "service_stop_date",
"fieldtype": "Date",
"label": "Service Stop Date"
"label": "Service Stop Date",
"no_copy": 1
},
{
"default": "0",
@@ -500,13 +502,15 @@
"depends_on": "enable_deferred_revenue",
"fieldname": "service_start_date",
"fieldtype": "Date",
"label": "Service Start Date"
"label": "Service Start Date",
"no_copy": 1
},
{
"depends_on": "enable_deferred_revenue",
"fieldname": "service_end_date",
"fieldtype": "Date",
"label": "Service End Date"
"label": "Service End Date",
"no_copy": 1
},
{
"collapsible": 1,
@@ -783,7 +787,8 @@
],
"idx": 1,
"istable": 1,
"modified": "2019-07-16 16:36:46.527606",
"links": [],
"modified": "2019-12-04 12:22:38.517710",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -1,299 +1,119 @@
{
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
"beta": 0,
"creation": "2013-01-10 16:34:09",
"custom": 0,
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"allow_import": 1,
"allow_rename": 1,
"creation": "2013-01-10 16:34:09",
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"title",
"is_default",
"disabled",
"column_break_3",
"company",
"tax_category",
"section_break_5",
"taxes"
],
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Title",
"length": 0,
"no_copy": 1,
"oldfieldname": "title",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"no_copy": 1,
"oldfieldname": "title",
"oldfieldtype": "Data",
"reqd": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_default",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Default",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"default": "0",
"fieldname": "is_default",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Default"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Disabled",
"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,
"unique": 0
},
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"length": 0,
"no_copy": 0,
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 1,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
"remember_last_selected_value": 1,
"reqd": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "section_break_5",
"fieldtype": "Section Break"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "* Will be calculated in the transaction.",
"fieldname": "taxes",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Sales Taxes and Charges",
"length": 0,
"no_copy": 0,
"oldfieldname": "other_charges",
"oldfieldtype": "Table",
"options": "Sales Taxes and Charges",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"description": "* Will be calculated in the transaction.",
"fieldname": "taxes",
"fieldtype": "Table",
"label": "Sales Taxes and Charges",
"oldfieldname": "other_charges",
"oldfieldtype": "Table",
"options": "Sales Taxes and Charges"
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category"
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-money",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-11-07 05:18:41.743257",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges Template",
"owner": "Administrator",
],
"icon": "fa fa-money",
"idx": 1,
"modified": "2019-11-25 13:06:03.279099",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges Template",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 1,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
},
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User"
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Master Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Master Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_order": "ASC",
"track_seen": 0
],
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}

View File

@@ -13,9 +13,9 @@ from frappe.utils import nowdate
class ShareDontExists(ValidationError): pass
class ShareTransfer(Document):
def before_submit(self):
def on_submit(self):
if self.transfer_type == 'Issue':
shareholder = self.get_shareholder_doc(self.company)
shareholder = self.get_company_shareholder()
shareholder.append('share_balance', {
'share_type': self.share_type,
'from_no': self.from_no,
@@ -28,7 +28,7 @@ class ShareTransfer(Document):
})
shareholder.save()
doc = frappe.get_doc('Shareholder', self.to_shareholder)
doc = self.get_shareholder_doc(self.to_shareholder)
doc.append('share_balance', {
'share_type': self.share_type,
'from_no': self.from_no,
@@ -41,11 +41,11 @@ class ShareTransfer(Document):
elif self.transfer_type == 'Purchase':
self.remove_shares(self.from_shareholder)
self.remove_shares(self.get_shareholder_doc(self.company).name)
self.remove_shares(self.get_company_shareholder().name)
elif self.transfer_type == 'Transfer':
self.remove_shares(self.from_shareholder)
doc = frappe.get_doc('Shareholder', self.to_shareholder)
doc = self.get_shareholder_doc(self.to_shareholder)
doc.append('share_balance', {
'share_type': self.share_type,
'from_no': self.from_no,
@@ -56,143 +56,127 @@ class ShareTransfer(Document):
})
doc.save()
def on_cancel(self):
if self.transfer_type == 'Issue':
compnay_shareholder = self.get_company_shareholder()
self.remove_shares(compnay_shareholder.name)
self.remove_shares(self.to_shareholder)
elif self.transfer_type == 'Purchase':
compnay_shareholder = self.get_company_shareholder()
from_shareholder = self.get_shareholder_doc(self.from_shareholder)
from_shareholder.append('share_balance', {
'share_type': self.share_type,
'from_no': self.from_no,
'to_no': self.to_no,
'rate': self.rate,
'amount': self.amount,
'no_of_shares': self.no_of_shares
})
from_shareholder.save()
compnay_shareholder.append('share_balance', {
'share_type': self.share_type,
'from_no': self.from_no,
'to_no': self.to_no,
'rate': self.rate,
'amount': self.amount,
'no_of_shares': self.no_of_shares
})
compnay_shareholder.save()
elif self.transfer_type == 'Transfer':
self.remove_shares(self.to_shareholder)
from_shareholder = self.get_shareholder_doc(self.from_shareholder)
from_shareholder.append('share_balance', {
'share_type': self.share_type,
'from_no': self.from_no,
'to_no': self.to_no,
'rate': self.rate,
'amount': self.amount,
'no_of_shares': self.no_of_shares
})
from_shareholder.save()
def validate(self):
self.get_company_shareholder()
self.basic_validations()
self.folio_no_validation()
if self.transfer_type == 'Issue':
if not self.get_shareholder_doc(self.company):
shareholder = frappe.get_doc({
'doctype': 'Shareholder',
'title': self.company,
'company': self.company,
'is_company': 1
})
shareholder.insert()
# validate share doesnt exist in company
ret_val = self.share_exists(self.get_shareholder_doc(self.company).name)
if ret_val != False:
# validate share doesn't exist in company
ret_val = self.share_exists(self.get_company_shareholder().name)
if ret_val in ('Complete', 'Partial'):
frappe.throw(_('The shares already exist'), frappe.DuplicateEntryError)
else:
# validate share exists with from_shareholder
ret_val = self.share_exists(self.from_shareholder)
if ret_val != True:
if ret_val in ('Outside', 'Partial'):
frappe.throw(_("The shares don't exist with the {0}")
.format(self.from_shareholder), ShareDontExists)
def basic_validations(self):
if self.transfer_type == 'Purchase':
self.to_shareholder = ''
if self.from_shareholder is None or self.from_shareholder is '':
if not self.from_shareholder:
frappe.throw(_('The field From Shareholder cannot be blank'))
if self.from_folio_no is None or self.from_folio_no is '':
if not self.from_folio_no:
self.to_folio_no = self.autoname_folio(self.to_shareholder)
if self.asset_account is None:
if not self.asset_account:
frappe.throw(_('The field Asset Account cannot be blank'))
elif (self.transfer_type == 'Issue'):
self.from_shareholder = ''
if self.to_shareholder is None or self.to_shareholder == '':
if not self.to_shareholder:
frappe.throw(_('The field To Shareholder cannot be blank'))
if self.to_folio_no is None or self.to_folio_no is '':
if not self.to_folio_no:
self.to_folio_no = self.autoname_folio(self.to_shareholder)
if self.asset_account is None:
if not self.asset_account:
frappe.throw(_('The field Asset Account cannot be blank'))
else:
if self.from_shareholder is None or self.to_shareholder is None:
if not self.from_shareholder or not self.to_shareholder:
frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank'))
if self.to_folio_no is None or self.to_folio_no is '':
if not self.to_folio_no:
self.to_folio_no = self.autoname_folio(self.to_shareholder)
if self.equity_or_liability_account is None:
if not self.equity_or_liability_account:
frappe.throw(_('The field Equity/Liability Account cannot be blank'))
if self.from_shareholder == self.to_shareholder:
frappe.throw(_('The seller and the buyer cannot be the same'))
if self.no_of_shares != self.to_no - self.from_no + 1:
frappe.throw(_('The number of shares and the share numbers are inconsistent'))
if self.amount is None:
if not self.amount:
self.amount = self.rate * self.no_of_shares
if self.amount != self.rate * self.no_of_shares:
frappe.throw(_('There are inconsistencies between the rate, no of shares and the amount calculated'))
def share_exists(self, shareholder):
# return True if exits,
# False if completely doesn't exist,
# 'partially exists' if partailly doesn't exist
ret_val = self.recursive_share_check(shareholder, self.share_type,
query = {
'from_no': self.from_no,
'to_no': self.to_no
}
)
if all(boolean == True for boolean in ret_val):
return True
elif True in ret_val:
return 'partially exists'
else:
return False
def recursive_share_check(self, shareholder, share_type, query):
# query = {'from_no': share_starting_no, 'to_no': share_ending_no}
# Recursive check if a given part of shares is held by the shareholder
# return a list containing True and False
# Eg. [True, False, True]
# All True implies its completely inside
# All False implies its completely outside
# A mix implies its partially inside/outside
does_share_exist = []
doc = frappe.get_doc('Shareholder', shareholder)
doc = self.get_shareholder_doc(shareholder)
for entry in doc.share_balance:
if entry.share_type != share_type or \
entry.from_no > query['to_no'] or \
entry.to_no < query['from_no']:
if entry.share_type != self.share_type or \
entry.from_no > self.to_no or \
entry.to_no < self.from_no:
continue # since query lies outside bounds
elif entry.from_no <= query['from_no'] and entry.to_no >= query['to_no']:
return [True] # absolute truth!
elif entry.from_no >= query['from_no'] and entry.to_no <= query['to_no']:
# split and check
does_share_exist.extend(self.recursive_share_check(shareholder,
share_type,
{
'from_no': query['from_no'],
'to_no': entry.from_no - 1
}
))
does_share_exist.append(True)
does_share_exist.extend(self.recursive_share_check(shareholder,
share_type,
{
'from_no': entry.to_no + 1,
'to_no': query['to_no']
}
))
elif query['from_no'] <= entry.from_no <= query['to_no'] and entry.to_no >= query['to_no']:
does_share_exist.extend(self.recursive_share_check(shareholder,
share_type,
{
'from_no': query['from_no'],
'to_no': entry.from_no - 1
}
))
elif query['from_no'] <= entry.to_no <= query['to_no'] and entry.from_no <= query['from_no']:
does_share_exist.extend(self.recursive_share_check(shareholder,
share_type,
{
'from_no': entry.to_no + 1,
'to_no': query['to_no']
}
))
elif entry.from_no <= self.from_no and entry.to_no >= self.to_no: #both inside
return 'Complete' # absolute truth!
elif entry.from_no <= self.from_no <= self.to_no:
return 'Partial'
elif entry.from_no <= self.to_no <= entry.to_no:
return 'Partial'
does_share_exist.append(False)
return does_share_exist
return 'Outside'
def folio_no_validation(self):
shareholders = ['from_shareholder', 'to_shareholder']
shareholders = [shareholder for shareholder in shareholders if self.get(shareholder) is not '']
for shareholder in shareholders:
doc = frappe.get_doc('Shareholder', self.get(shareholder))
doc = self.get_shareholder_doc(self.get(shareholder))
if doc.company != self.company:
frappe.throw(_('The shareholder does not belong to this company'))
if doc.folio_no is '' or doc.folio_no is None:
if not doc.folio_no:
doc.folio_no = self.from_folio_no \
if (shareholder == 'from_shareholder') else self.to_folio_no;
if (shareholder == 'from_shareholder') else self.to_folio_no
doc.save()
else:
if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder == 'from_shareholder') else self.to_folio_no):
@@ -200,24 +184,14 @@ class ShareTransfer(Document):
def autoname_folio(self, shareholder, is_company=False):
if is_company:
doc = self.get_shareholder_doc(shareholder)
doc = self.get_company_shareholder()
else:
doc = frappe.get_doc('Shareholder' , shareholder)
doc = self.get_shareholder_doc(shareholder)
doc.folio_no = make_autoname('FN.#####')
doc.save()
return doc.folio_no
def remove_shares(self, shareholder):
self.iterative_share_removal(shareholder, self.share_type,
{
'from_no': self.from_no,
'to_no' : self.to_no
},
rate = self.rate,
amount = self.amount
)
def iterative_share_removal(self, shareholder, share_type, query, rate, amount):
# query = {'from_no': share_starting_no, 'to_no': share_ending_no}
# Shares exist for sure
# Iterate over all entries and modify entry if in entry
@@ -227,31 +201,31 @@ class ShareTransfer(Document):
for entry in current_entries:
# use spaceage logic here
if entry.share_type != share_type or \
entry.from_no > query['to_no'] or \
entry.to_no < query['from_no']:
if entry.share_type != self.share_type or \
entry.from_no > self.to_no or \
entry.to_no < self.from_no:
new_entries.append(entry)
continue # since query lies outside bounds
elif entry.from_no <= query['from_no'] and entry.to_no >= query['to_no']:
elif entry.from_no <= self.from_no and entry.to_no >= self.to_no:
#split
if entry.from_no == query['from_no']:
if entry.to_no == query['to_no']:
if entry.from_no == self.from_no:
if entry.to_no == self.to_no:
pass #nothing to append
else:
new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
else:
if entry.to_no == query['to_no']:
new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
if entry.to_no == self.to_no:
new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
else:
new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
elif entry.from_no >= query['from_no'] and entry.to_no <= query['to_no']:
new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
elif entry.from_no >= self.from_no and entry.to_no <= self.to_no:
# split and check
pass #nothing to append
elif query['from_no'] <= entry.from_no <= query['to_no'] and entry.to_no >= query['to_no']:
new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
elif query['from_no'] <= entry.to_no <= query['to_no'] and entry.from_no <= query['from_no']:
new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
elif self.from_no <= entry.from_no <= self.to_no and entry.to_no >= self.to_no:
new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
elif self.from_no <= entry.to_no <= self.to_no and entry.from_no <= self.from_no:
new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
else:
new_entries.append(entry)
@@ -272,16 +246,34 @@ class ShareTransfer(Document):
}
def get_shareholder_doc(self, shareholder):
# Get Shareholder doc based on the Shareholder title
doc = frappe.get_list('Shareholder',
filters = [
('Shareholder', 'title', '=', shareholder)
]
)
if len(doc) == 1:
return frappe.get_doc('Shareholder', doc[0]['name'])
else: #It will necessarily by 0 indicating it doesn't exist
return False
# Get Shareholder doc based on the Shareholder name
if shareholder:
query_filters = {'name': shareholder}
name = frappe.db.get_value('Shareholder', {'name': shareholder}, 'name')
return frappe.get_doc('Shareholder', name)
def get_company_shareholder(self):
# Get company doc or create one if not present
company_shareholder = frappe.db.get_value('Shareholder',
{
'company': self.company,
'is_company': 1
}, 'name')
if company_shareholder:
return frappe.get_doc('Shareholder', company_shareholder)
else:
shareholder = frappe.get_doc({
'doctype': 'Shareholder',
'title': self.company,
'company': self.company,
'is_company': 1
})
shareholder.insert()
return shareholder
@frappe.whitelist()
def make_jv_entry( company, account, amount, payment_account,\

View File

@@ -15,73 +15,73 @@ class TestShareTransfer(unittest.TestCase):
frappe.db.sql("delete from `tabShare Balance`")
share_transfers = [
{
"doctype" : "Share Transfer",
"transfer_type" : "Issue",
"date" : "2018-01-01",
"to_shareholder" : "SH-00001",
"share_type" : "Equity",
"from_no" : 1,
"to_no" : 500,
"no_of_shares" : 500,
"rate" : 10,
"company" : "_Test Company",
"asset_account" : "Cash - _TC",
"doctype": "Share Transfer",
"transfer_type": "Issue",
"date": "2018-01-01",
"to_shareholder": "SH-00001",
"share_type": "Equity",
"from_no": 1,
"to_no": 500,
"no_of_shares": 500,
"rate": 10,
"company": "_Test Company",
"asset_account": "Cash - _TC",
"equity_or_liability_account": "Creditors - _TC"
},
{
"doctype" : "Share Transfer",
"transfer_type" : "Transfer",
"date" : "2018-01-02",
"from_shareholder" : "SH-00001",
"to_shareholder" : "SH-00002",
"share_type" : "Equity",
"from_no" : 101,
"to_no" : 200,
"no_of_shares" : 100,
"rate" : 15,
"company" : "_Test Company",
"doctype": "Share Transfer",
"transfer_type": "Transfer",
"date": "2018-01-02",
"from_shareholder": "SH-00001",
"to_shareholder": "SH-00002",
"share_type": "Equity",
"from_no": 101,
"to_no": 200,
"no_of_shares": 100,
"rate": 15,
"company": "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
},
{
"doctype" : "Share Transfer",
"transfer_type" : "Transfer",
"date" : "2018-01-03",
"from_shareholder" : "SH-00001",
"to_shareholder" : "SH-00003",
"share_type" : "Equity",
"from_no" : 201,
"to_no" : 500,
"no_of_shares" : 300,
"rate" : 20,
"company" : "_Test Company",
"doctype": "Share Transfer",
"transfer_type": "Transfer",
"date": "2018-01-03",
"from_shareholder": "SH-00001",
"to_shareholder": "SH-00003",
"share_type": "Equity",
"from_no": 201,
"to_no": 500,
"no_of_shares": 300,
"rate": 20,
"company": "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
},
{
"doctype" : "Share Transfer",
"transfer_type" : "Transfer",
"date" : "2018-01-04",
"from_shareholder" : "SH-00003",
"to_shareholder" : "SH-00002",
"share_type" : "Equity",
"from_no" : 201,
"to_no" : 400,
"no_of_shares" : 200,
"rate" : 15,
"company" : "_Test Company",
"doctype": "Share Transfer",
"transfer_type": "Transfer",
"date": "2018-01-04",
"from_shareholder": "SH-00003",
"to_shareholder": "SH-00002",
"share_type": "Equity",
"from_no": 201,
"to_no": 400,
"no_of_shares": 200,
"rate": 15,
"company": "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
},
{
"doctype" : "Share Transfer",
"transfer_type" : "Purchase",
"date" : "2018-01-05",
"from_shareholder" : "SH-00003",
"share_type" : "Equity",
"from_no" : 401,
"to_no" : 500,
"no_of_shares" : 100,
"rate" : 25,
"company" : "_Test Company",
"asset_account" : "Cash - _TC",
"doctype": "Share Transfer",
"transfer_type": "Purchase",
"date": "2018-01-05",
"from_shareholder": "SH-00003",
"share_type": "Equity",
"from_no": 401,
"to_no": 500,
"no_of_shares": 100,
"rate": 25,
"company": "_Test Company",
"asset_account": "Cash - _TC",
"equity_or_liability_account": "Creditors - _TC"
}
]
@@ -91,33 +91,33 @@ class TestShareTransfer(unittest.TestCase):
def test_invalid_share_transfer(self):
doc = frappe.get_doc({
"doctype" : "Share Transfer",
"transfer_type" : "Transfer",
"date" : "2018-01-05",
"from_shareholder" : "SH-00003",
"to_shareholder" : "SH-00002",
"share_type" : "Equity",
"from_no" : 1,
"to_no" : 100,
"no_of_shares" : 100,
"rate" : 15,
"company" : "_Test Company",
"doctype": "Share Transfer",
"transfer_type": "Transfer",
"date": "2018-01-05",
"from_shareholder": "SH-00003",
"to_shareholder": "SH-00002",
"share_type": "Equity",
"from_no": 1,
"to_no": 100,
"no_of_shares": 100,
"rate": 15,
"company": "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
})
self.assertRaises(ShareDontExists, doc.insert)
doc = frappe.get_doc({
"doctype" : "Share Transfer",
"transfer_type" : "Purchase",
"date" : "2018-01-02",
"from_shareholder" : "SH-00001",
"share_type" : "Equity",
"from_no" : 1,
"to_no" : 200,
"no_of_shares" : 200,
"rate" : 15,
"company" : "_Test Company",
"asset_account" : "Cash - _TC",
"doctype": "Share Transfer",
"transfer_type": "Purchase",
"date": "2018-01-02",
"from_shareholder": "SH-00001",
"share_type": "Equity",
"from_no": 1,
"to_no": 200,
"no_of_shares": 200,
"rate": 15,
"company": "_Test Company",
"asset_account": "Cash - _TC",
"equity_or_liability_account": "Creditors - _TC"
})
self.assertRaises(ShareDontExists, doc.insert)

View File

@@ -1,587 +1,163 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "naming_series:",
"beta": 0,
"creation": "2017-12-25 16:50:53.878430",
"custom": 0,
"description": "",
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"autoname": "naming_series:",
"creation": "2017-12-25 16:50:53.878430",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"title",
"column_break_2",
"naming_series",
"section_break_2",
"folio_no",
"column_break_4",
"company",
"is_company",
"address_contacts",
"address_html",
"column_break_9",
"contact_html",
"section_break_3",
"share_balance",
"contact_list"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title",
"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": "Title",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"options": "ACC-SH-.YYYY.-",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "naming_series",
"fieldtype": "Select",
"options": "ACC-SH-.YYYY.-"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "folio_no",
"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": "Folio no.",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"fieldname": "folio_no",
"fieldtype": "Data",
"label": "Folio no.",
"read_only": 1,
"unique": 1
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_company",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Company",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "0",
"fieldname": "is_company",
"fieldtype": "Check",
"hidden": 1,
"label": "Is Company",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address and Contacts",
"length": 0,
"no_copy": 0,
"options": "fa fa-map-marker",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"label": "Address and Contacts",
"options": "fa fa-map-marker"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address_html",
"fieldtype": "HTML",
"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": "Address HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "address_html",
"fieldtype": "HTML",
"label": "Address HTML",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "contact_html",
"fieldtype": "HTML",
"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": "Contact HTML",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "contact_html",
"fieldtype": "HTML",
"label": "Contact HTML",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Share Balance",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"label": "Share Balance"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "share_balance",
"fieldtype": "Table",
"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": "Share Balance",
"length": 0,
"no_copy": 0,
"options": "Share Balance",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "share_balance",
"fieldtype": "Table",
"label": "Share Balance",
"options": "Share Balance",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Hidden list maintaining the list of contacts linked to Shareholder",
"fieldname": "contact_list",
"fieldtype": "Code",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact List",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"description": "Hidden list maintaining the list of contacts linked to Shareholder",
"fieldname": "contact_list",
"fieldtype": "Code",
"hidden": 1,
"label": "Contact List",
"read_only": 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-09-18 14:14:24.953014",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Shareholder",
"name_case": "Title Case",
"owner": "Administrator",
],
"modified": "2019-11-17 23:24:11.395882",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Shareholder",
"name_case": "Title 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,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"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,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"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,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "folio_no",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "title",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"search_fields": "folio_no",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "title",
"track_changes": 1
}

View File

@@ -70,7 +70,7 @@ class ShippingRule(Document):
def get_shipping_amount_from_rules(self, value):
for condition in self.get("conditions"):
if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)):
if not condition.to_value or (flt(condition.from_value) <= flt(value) <= flt(condition.to_value)):
return condition.shipping_amount
return 0.0

View File

@@ -90,8 +90,12 @@ def merge_similar_entries(gl_map):
else:
merged_gl_map.append(entry)
company = gl_map[0].company if gl_map else erpnext.get_default_company()
company_currency = erpnext.get_company_currency(company)
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
# filter zero debit and credit entries
merged_gl_map = filter(lambda x: flt(x.debit, 9)!=0 or flt(x.credit, 9)!=0, merged_gl_map)
merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map)
merged_gl_map = list(merged_gl_map)
return merged_gl_map

View File

@@ -23,7 +23,7 @@ class DuplicatePartyAccountError(frappe.ValidationError): pass
@frappe.whitelist()
def get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False, fetch_payment_terms_template=True,
party_address=None, shipping_address=None, pos_profile=None):
party_address=None, company_address=None, shipping_address=None, pos_profile=None):
if not party:
return {}
@@ -31,14 +31,14 @@ def get_party_details(party=None, account=None, party_type="Customer", company=N
frappe.throw(_("{0}: {1} does not exists").format(party_type, party))
return _get_party_details(party, account, party_type,
company, posting_date, bill_date, price_list, currency, doctype, ignore_permissions,
fetch_payment_terms_template, party_address, shipping_address, pos_profile)
fetch_payment_terms_template, party_address, company_address, shipping_address, pos_profile)
def _get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False,
fetch_payment_terms_template=True, party_address=None, shipping_address=None, pos_profile=None):
fetch_payment_terms_template=True, party_address=None, company_address=None,shipping_address=None, pos_profile=None):
out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
party = out[party_type.lower()]
party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
party = party_details[party_type.lower()]
if not ignore_permissions and not frappe.has_permission(party_type, "read", party):
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
@@ -46,76 +46,81 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
party = frappe.get_doc(party_type, party)
currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
party_address, shipping_address = set_address_details(out, party, party_type, doctype, company, party_address, shipping_address)
set_contact_details(out, party, party_type)
set_other_values(out, party, party_type)
set_price_list(out, party, party_type, price_list, pos_profile)
party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
set_contact_details(party_details, party, party_type)
set_other_values(party_details, party, party_type)
set_price_list(party_details, party, party_type, price_list, pos_profile)
out["tax_category"] = get_address_tax_category(party.get("tax_category"),
party_details["tax_category"] = get_address_tax_category(party.get("tax_category"),
party_address, shipping_address if party_type != "Supplier" else party_address)
out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company,
customer_group=out.customer_group, supplier_group=out.supplier_group, tax_category=out.tax_category,
billing_address=party_address, shipping_address=shipping_address)
if not party_details.get("taxes_and_charges"):
party_details["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company,
customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
billing_address=party_address, shipping_address=shipping_address)
if fetch_payment_terms_template:
out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
party_details["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
if not out.get("currency"):
out["currency"] = currency
if not party_details.get("currency"):
party_details["currency"] = currency
# sales team
if party_type=="Customer":
out["sales_team"] = [{
party_details["sales_team"] = [{
"sales_person": d.sales_person,
"allocated_percentage": d.allocated_percentage or None
} for d in party.get("sales_team")]
# supplier tax withholding category
if party_type == "Supplier" and party:
out["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
party_details["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
return out
return party_details
def set_address_details(out, party, party_type, doctype=None, company=None, party_address=None, shipping_address=None):
def set_address_details(party_details, party, party_type, doctype=None, company=None, party_address=None, company_address=None, shipping_address=None):
billing_address_field = "customer_address" if party_type == "Lead" \
else party_type.lower() + "_address"
out[billing_address_field] = party_address or get_default_address(party_type, party.name)
party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
if doctype:
out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field]))
party_details.update(get_fetch_values(doctype, billing_address_field, party_details[billing_address_field]))
# address display
out.address_display = get_address_display(out[billing_address_field])
party_details.address_display = get_address_display(party_details[billing_address_field])
# shipping address
if party_type in ["Customer", "Lead"]:
out.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name)
out.shipping_address = get_address_display(out["shipping_address_name"])
party_details.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name)
party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
if doctype:
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
party_details.update(get_fetch_values(doctype, 'shipping_address_name', party_details.shipping_address_name))
if doctype and doctype in ['Delivery Note', 'Sales Invoice']:
out.update(get_company_address(company))
if out.company_address:
out.update(get_fetch_values(doctype, 'company_address', out.company_address))
get_regional_address_details(out, doctype, company)
if company_address:
party_details.update({'company_address': company_address})
else:
party_details.update(get_company_address(company))
elif doctype and doctype == "Purchase Invoice":
out.update(get_company_address(company))
if out.company_address:
out["shipping_address"] = shipping_address or out["company_address"]
out.shipping_address_display = get_address_display(out["shipping_address"])
out.update(get_fetch_values(doctype, 'shipping_address', out.shipping_address))
get_regional_address_details(out, doctype, company)
if doctype and doctype in ['Delivery Note', 'Sales Invoice', 'Sales Order']:
if party_details.company_address:
party_details.update(get_fetch_values(doctype, 'company_address', party_details.company_address))
get_regional_address_details(party_details, doctype, company)
return out.get(billing_address_field), out.shipping_address_name
elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
if party_details.company_address:
party_details["shipping_address"] = shipping_address or party_details["company_address"]
party_details.shipping_address_display = get_address_display(party_details["shipping_address"])
party_details.update(get_fetch_values(doctype, 'shipping_address', party_details.shipping_address))
get_regional_address_details(party_details, doctype, company)
return party_details.get(billing_address_field), party_details.shipping_address_name
@erpnext.allow_regional
def get_regional_address_details(out, doctype, company):
def get_regional_address_details(party_details, doctype, company):
pass
def set_contact_details(out, party, party_type):
out.contact_person = get_default_contact(party_type, party.name)
def set_contact_details(party_details, party, party_type):
party_details.contact_person = get_default_contact(party_type, party.name)
if not out.contact_person:
out.update({
if not party_details.contact_person:
party_details.update({
"contact_person": None,
"contact_display": None,
"contact_email": None,
@@ -125,22 +130,22 @@ def set_contact_details(out, party, party_type):
"contact_department": None
})
else:
out.update(get_contact_details(out.contact_person))
party_details.update(get_contact_details(party_details.contact_person))
def set_other_values(out, party, party_type):
def set_other_values(party_details, party, party_type):
# copy
if party_type=="Customer":
to_copy = ["customer_name", "customer_group", "territory", "language"]
else:
to_copy = ["supplier_name", "supplier_group", "language"]
for f in to_copy:
out[f] = party.get(f)
party_details[f] = party.get(f)
# fields prepended with default in Customer doctype
for f in ['currency'] \
+ (['sales_partner', 'commission_rate'] if party_type=="Customer" else []):
if party.get("default_" + f):
out[f] = party.get("default_" + f)
party_details[f] = party.get("default_" + f)
def get_default_price_list(party):
"""Return default price list for party (Document object)"""
@@ -155,7 +160,7 @@ def get_default_price_list(party):
return None
def set_price_list(out, party, party_type, given_price_list, pos=None):
def set_price_list(party_details, party, party_type, given_price_list, pos=None):
# price list
price_list = get_permitted_documents('Price List')
@@ -173,9 +178,9 @@ def set_price_list(out, party, party_type, given_price_list, pos=None):
price_list = get_default_price_list(party) or given_price_list
if price_list:
out.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True)
party_details.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True)
out["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list
party_details["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list
def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype):

View File

@@ -100,6 +100,11 @@ frappe.query_reports["Accounts Payable"] = {
"fieldtype": "Link",
"options": "Supplier Group"
},
{
"fieldname":"based_on_payment_terms",
"label": __("Based On Payment Terms"),
"fieldtype": "Check",
},
{
"fieldname":"tax_id",
"label": __("Tax Id"),

View File

@@ -88,6 +88,11 @@ frappe.query_reports["Accounts Payable Summary"] = {
"label": __("Supplier Group"),
"fieldtype": "Link",
"options": "Supplier Group"
},
{
"fieldname":"based_on_payment_terms",
"label": __("Based On Payment Terms"),
"fieldtype": "Check",
}
],

View File

@@ -60,6 +60,7 @@ class ReceivablePayableReport(object):
def get_data(self):
self.get_gl_entries()
self.get_sales_invoices_or_customers_based_on_sales_person()
self.voucher_balance = OrderedDict()
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
@@ -103,12 +104,18 @@ class ReceivablePayableReport(object):
def get_invoices(self, gle):
if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'):
self.invoices.add(gle.voucher_no)
if self.filters.get("sales_person"):
if gle.voucher_no in self.sales_person_records.get("Sales Invoice", []) \
or gle.party in self.sales_person_records.get("Customer", []):
self.invoices.add(gle.voucher_no)
else:
self.invoices.add(gle.voucher_no)
def update_voucher_balance(self, gle):
# get the row where this balance needs to be updated
# if its a payment, it will return the linked invoice or will be considered as advance
row = self.get_voucher_balance(gle)
if not row: return
# gle_balance will be the total "debit - credit" for receivable type reports and
# and vice-versa for payable type reports
gle_balance = self.get_gle_balance(gle)
@@ -129,8 +136,13 @@ class ReceivablePayableReport(object):
row.paid -= gle_balance
def get_voucher_balance(self, gle):
voucher_balance = None
if self.filters.get("sales_person"):
against_voucher = gle.against_voucher or gle.voucher_no
if not (gle.party in self.sales_person_records.get("Customer", []) or \
against_voucher in self.sales_person_records.get("Sales Invoice", [])):
return
voucher_balance = None
if gle.against_voucher:
# find invoice
against_voucher = gle.against_voucher
@@ -318,7 +330,7 @@ class ReceivablePayableReport(object):
self.append_payment_term(row, d, term)
def append_payment_term(self, row, d, term):
if self.filters.get("customer") and d.currency == d.party_account_currency:
if (self.filters.get("customer") or self.filters.get("supplier")) and d.currency == d.party_account_currency:
invoiced = d.payment_amount
else:
invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision)
@@ -512,6 +524,22 @@ class ReceivablePayableReport(object):
order by posting_date, party"""
.format(select_fields, conditions), values, as_dict=True)
def get_sales_invoices_or_customers_based_on_sales_person(self):
if self.filters.get("sales_person"):
lft, rgt = frappe.db.get_value("Sales Person",
self.filters.get("sales_person"), ["lft", "rgt"])
records = frappe.db.sql("""
select distinct parent, parenttype
from `tabSales Team` steam
where parenttype in ('Customer', 'Sales Invoice')
and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person)
""", (lft, rgt), as_dict=1)
self.sales_person_records = frappe._dict()
for d in records:
self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
def prepare_conditions(self):
conditions = [""]
values = [self.party_type, self.filters.report_date]
@@ -564,16 +592,6 @@ class ReceivablePayableReport(object):
conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)")
values.append(self.filters.get("sales_partner"))
if self.filters.get("sales_person"):
lft, rgt = frappe.db.get_value("Sales Person",
self.filters.get("sales_person"), ["lft", "rgt"])
conditions.append("""exists(select name from `tabSales Team` steam where
steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1})
and ((steam.parent = voucher_no and steam.parenttype = voucher_type)
or (steam.parent = against_voucher and steam.parenttype = against_voucher_type)
or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt))
def add_supplier_filters(self, conditions, values):
if self.filters.get("supplier_group"):
conditions.append("""party in (select name from tabSupplier

View File

@@ -106,6 +106,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
"label": __("Sales Person"),
"fieldtype": "Link",
"options": "Sales Person"
},
{
"fieldname":"based_on_payment_terms",
"label": __("Based On Payment Terms"),
"fieldtype": "Check",
}
],

View File

@@ -36,7 +36,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
self.filters.report_date) or {}
for party, party_dict in iteritems(self.party_total):
if party_dict.outstanding <= 0:
if party_dict.outstanding == 0:
continue
row = frappe._dict()

View File

@@ -1,5 +1,6 @@
{%
var report_columns = report.get_columns_for_print();
report_columns = report_columns.filter(col => !col.hidden);
if (report_columns.length > 8) {
frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application."));
@@ -15,34 +16,35 @@
height: 37px;
}
</style>
{% var letterhead= filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) %}
{% if(letterhead) { %}
<div style="margin-bottom: 7px;" class="text-center">
{%= frappe.boot.letter_heads[letterhead].header %}
</div>
{% } %}
<h2 class="text-center">{%= __(report.report_name) %}</h2>
<h3 class="text-center">{%= filters.company %}</h3>
{% if 'cost_center' in filters %}
<h3 class="text-center">{%= filters.cost_center %}</h3>
{% endif %}
<h3 class="text-center">{%= filters.fiscal_year %}</h3>
<h5 class="text-center">{%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %} </h4>
<h5 class="text-center">
{%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
</h5>
{% if (filters.from_date) { %}
<h4 class="text-center">{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}</h3>
<h5 class="text-center">
{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}
</h5>
{% } %}
<hr>
<table class="table table-bordered">
<thead>
<tr>
<th style="width: {%= 100 - (report_columns.length - 2) * 13 %}%"></th>
{% for(var i=2, l=report_columns.length; i<l; i++) { %}
<th style="width: {%= 100 - (report_columns.length - 1) * 13 %}%"></th>
{% for (let i=1, l=report_columns.length; i<l; i++) { %}
<th class="text-right">{%= report_columns[i].label %}</th>
{% } %}
</tr>
</thead>
<tbody>
{% for(var j=0, k=data.length-1; j<k; j++) { %}
{% for(let j=0, k=data.length-1; j<k; j++) { %}
{%
var row = data[j];
var row_class = data[j].parent_account ? "" : "financial-statements-important";
@@ -52,11 +54,11 @@
<td>
<span style="padding-left: {%= cint(data[j].indent) * 2 %}em">{%= row.account_name %}</span>
</td>
{% for(var i=2, l=report_columns.length; i<l; i++) { %}
{% for(let i=1, l=report_columns.length; i<l; i++) { %}
<td class="text-right">
{% var fieldname = report_columns[i].fieldname; %}
{% const fieldname = report_columns[i].fieldname; %}
{% if (!is_null(row[fieldname])) { %}
{%= format_currency(row[fieldname], filters.presentation_currency) %}
{%= frappe.format(row[fieldname], report_columns[i], {}, row) %}
{% } %}
</td>
{% } %}
@@ -64,4 +66,6 @@
{% } %}
</tbody>
</table>
<p class="text-right text-muted">Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
<p class="text-right text-muted">
Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}
</p>

View File

@@ -264,8 +264,8 @@ def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False
def add_total_row(out, root_type, balance_must_be, period_list, company_currency):
total_row = {
"account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
"account": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
"account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
"account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
"currency": company_currency
}

View File

@@ -18,14 +18,17 @@ def execute(filters=None):
return columns, data
def get_data(filters, show_party_name):
party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
if filters.get('party_type') in ('Customer', 'Supplier', 'Employee', 'Member'):
party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
if filters.get('party_type') == 'Student':
party_name_field = 'first_name'
elif filters.get('party_type') == 'Shareholder':
party_name_field = 'title'
else:
party_name_field = 'name'
party_filters = {"name": filters.get("party")} if filters.get("party") else {}
parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],
parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],
filters = party_filters, order_by="name")
company_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
opening_balances = get_opening_balances(filters)
@@ -70,7 +73,7 @@ def get_data(filters, show_party_name):
# totals
for col in total_row:
total_row[col] += row.get(col)
row.update({
"currency": company_currency
})
@@ -78,7 +81,7 @@ def get_data(filters, show_party_name):
has_value = False
if (opening_debit or opening_credit or debit or credit or closing_debit or closing_credit):
has_value =True
if cint(filters.show_zero_values) or has_value:
data.append(row)
@@ -94,9 +97,9 @@ def get_data(filters, show_party_name):
def get_opening_balances(filters):
gle = frappe.db.sql("""
select party, sum(debit) as opening_debit, sum(credit) as opening_credit
select party, sum(debit) as opening_debit, sum(credit) as opening_credit
from `tabGL Entry`
where company=%(company)s
where company=%(company)s
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
group by party""", {
@@ -114,11 +117,11 @@ def get_opening_balances(filters):
def get_balances_within_period(filters):
gle = frappe.db.sql("""
select party, sum(debit) as debit, sum(credit) as credit
select party, sum(debit) as debit, sum(credit) as credit
from `tabGL Entry`
where company=%(company)s
where company=%(company)s
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
and posting_date >= %(from_date)s and posting_date <= %(to_date)s
and posting_date >= %(from_date)s and posting_date <= %(to_date)s
and ifnull(is_opening, 'No') = 'No'
group by party""", {
"company": filters.company,

View File

@@ -569,7 +569,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None)
warehouse_account = get_warehouse_account_map(company)
account_balance = get_balance_on(account, posting_date, in_account_currency=False)
account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
related_warehouses = [wh for wh, wh_details in warehouse_account.items()
if wh_details.account == account and not wh_details.is_group]

View File

@@ -33,7 +33,7 @@ class Asset(AccountsController):
self.make_asset_movement()
if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.asset_category):
self.make_gl_entries()
def before_cancel(self):
self.cancel_auto_gen_movement()
@@ -43,7 +43,7 @@ class Asset(AccountsController):
self.set_status()
delete_gl_entries(voucher_type='Asset', voucher_no=self.name)
self.db_set('booked_fixed_asset', 0)
def validate_asset_and_reference(self):
if self.purchase_invoice or self.purchase_receipt:
reference_doc = 'Purchase Invoice' if self.purchase_invoice else 'Purchase Receipt'
@@ -51,8 +51,8 @@ class Asset(AccountsController):
reference_doc = frappe.get_doc(reference_doc, reference_name)
if reference_doc.get('company') != self.company:
frappe.throw(_("Company of asset {0} and purchase document {1} doesn't matches.").format(self.name, reference_doc.get('name')))
if self.is_existing_asset and self.purchase_invoice:
frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
@@ -135,7 +135,7 @@ class Asset(AccountsController):
movement = frappe.get_doc('Asset Movement', movements[0].get('name'))
movement.flags.ignore_validate = True
movement.cancel()
def make_asset_movement(self):
reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
reference_docname = self.purchase_receipt or self.purchase_invoice
@@ -204,7 +204,7 @@ class Asset(AccountsController):
if has_pro_rata and n==0:
depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount,
self.available_for_use_date, d.depreciation_start_date)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
monthly_schedule_date = add_months(d.depreciation_start_date, - months + 1)
@@ -262,7 +262,7 @@ class Asset(AccountsController):
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / month_range
self.append("schedules", {
"schedule_date": date,
"depreciation_amount": amount,
@@ -517,15 +517,18 @@ def update_maintenance_status():
asset.set_status('Out of Order')
def make_post_gl_entry():
if not is_cwip_accounting_enabled(self.asset_category):
return
assets = frappe.db.sql_list(""" select name from `tabAsset`
where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate())
asset_categories = frappe.db.get_all('Asset Category', fields = ['name', 'enable_cwip_accounting'])
for asset in assets:
doc = frappe.get_doc('Asset', asset)
doc.make_gl_entries()
for asset_category in asset_categories:
if cint(asset_category.enable_cwip_accounting):
assets = frappe.db.sql_list(""" select name from `tabAsset`
where asset_category = %s and ifnull(booked_fixed_asset, 0) = 0
and available_for_use_date = %s""", (asset_category.name, nowdate()))
for asset in assets:
doc = frappe.get_doc('Asset', asset)
doc.make_gl_entries()
def get_asset_naming_series():
meta = frappe.get_meta('Asset')
@@ -607,13 +610,19 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non
if asset:
account = get_asset_category_account(account_name, asset=asset,
asset_category = asset_category, company = company)
if not asset and not account:
account = get_asset_category_account(account_name, asset_category = asset_category, company = company)
if not account:
account = frappe.get_cached_value('Company', company, account_name)
if not account:
frappe.throw(_("Set {0} in asset category {1} or company {2}")
.format(account_name.replace('_', ' ').title(), asset_category, company))
if not asset_category:
frappe.throw(_("Set {0} in company {2}").format(account_name.replace('_', ' ').title(), company))
else:
frappe.throw(_("Set {0} in asset category {1} or company {2}")
.format(account_name.replace('_', ' ').title(), asset_category, company))
return account
@@ -652,10 +661,10 @@ def make_journal_entry(asset_name):
def make_asset_movement(assets, purpose=None):
import json
from six import string_types
if isinstance(assets, string_types):
assets = json.loads(assets)
if len(assets) == 0:
frappe.throw(_('Atleast one asset has to be selected.'))

View File

@@ -26,5 +26,11 @@ frappe.query_reports["Fixed Asset Register"] = {
fieldtype: "Link",
options: "Finance Book"
},
{
fieldname:"date",
label: __("Date"),
fieldtype: "Date",
default: frappe.datetime.get_today()
},
]
};

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cstr
from frappe.utils import cstr, today, flt
def execute(filters=None):
filters = frappe._dict(filters or {})
@@ -86,8 +86,8 @@ def get_columns(filters):
"width": 90
},
{
"label": _("Current Value"),
"fieldname": "current_value",
"label": _("Asset Value"),
"fieldname": "asset_value",
"options": "Currency",
"width": 90
},
@@ -114,7 +114,7 @@ def get_data(filters):
data = []
conditions = get_conditions(filters)
current_value_map = get_finance_book_value_map(filters.finance_book)
depreciation_amount_map = get_finance_book_value_map(filters.date, filters.finance_book)
pr_supplier_map = get_purchase_receipt_supplier_map()
pi_supplier_map = get_purchase_invoice_supplier_map()
@@ -125,7 +125,9 @@ def get_data(filters):
"available_for_use_date", "status", "purchase_invoice"])
for asset in assets_record:
if current_value_map.get(asset.name) is not None:
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
- flt(depreciation_amount_map.get(asset.name))
if asset_value:
row = {
"asset_id": asset.name,
"asset_name": asset.asset_name,
@@ -138,19 +140,24 @@ def get_data(filters):
"location": asset.location,
"asset_category": asset.asset_category,
"purchase_date": asset.purchase_date,
"current_value": current_value_map.get(asset.name)
"asset_value": asset_value
}
data.append(row)
return data
def get_finance_book_value_map(finance_book=''):
def get_finance_book_value_map(date, finance_book=''):
if not date:
date = today()
return frappe._dict(frappe.db.sql(''' Select
parent, value_after_depreciation
FROM `tabAsset Finance Book`
parent, SUM(depreciation_amount)
FROM `tabDepreciation Schedule`
WHERE
parentfield='finance_books'
AND ifnull(finance_book, '')=%s''', cstr(finance_book)))
parentfield='schedules'
AND schedule_date<=%s
AND journal_entry IS NOT NULL
AND ifnull(finance_book, '')=%s
GROUP BY parent''', (date, cstr(finance_book))))
def get_purchase_receipt_supplier_map():
return frappe._dict(frappe.db.sql(''' Select

View File

@@ -18,6 +18,7 @@ frappe.ui.form.on("Purchase Order", {
return {
filters: {
"company": frm.doc.company,
"name": ['!=', frm.doc.supplier_warehouse],
"is_group": 0
}
}
@@ -283,6 +284,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
})
}
me.dialog.get_field('sub_con_rm_items').check_all_rows()
me.dialog.show()
this.dialog.set_primary_action(__('Transfer'), function() {
me.values = me.dialog.get_values();

View File

@@ -0,0 +1,3 @@
{% include "erpnext/regional/india/taxes.js" %}
erpnext.setup_auto_gst_taxation('Purchase Order');

View File

@@ -519,47 +519,62 @@ class TestPurchaseOrder(unittest.TestCase):
def test_backflush_based_on_stock_entry(self):
item_code = "_Test Subcontracted FG Item 1"
make_subcontracted_item(item_code)
make_item('Sub Contracted Raw Material 1', {
'is_stock_item': 1,
'is_sub_contracted_item': 1
})
update_backflush_based_on("Material Transferred for Subcontract")
po = create_purchase_order(item_code=item_code, qty=1,
order_qty = 5
po = create_purchase_order(item_code=item_code, qty=order_qty,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Test Extra Item 1", qty=100, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Test Extra Item 2", qty=10, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100)
rm_item = [
{"item_code":item_code,"rm_item_code":"_Test Item","item_name":"_Test Item",
"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":100,"stock_uom":"Nos"},
rm_items = [
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item",
"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
{"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100",
"qty":2,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"},
"qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
{"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1",
"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}]
"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
{'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos',
'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}]
rm_item_string = json.dumps(rm_item)
rm_item_string = json.dumps(rm_items)
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
se.append('items', {
'item_code': "Test Extra Item 2",
"qty": 1,
"rate": 100,
"s_warehouse": "_Test Warehouse - _TC",
"t_warehouse": "_Test Warehouse 1 - _TC"
})
se.set_missing_values()
se.submit()
pr = make_purchase_receipt(po.name)
received_qty = 2
# partial receipt
pr.get('items')[0].qty = received_qty
pr.save()
pr.submit()
se_items = sorted([d.item_code for d in se.get('items')])
supplied_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name])
issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
self.assertEquals(transferred_items, issued_items)
self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000)
transferred_rm_map = frappe._dict()
for item in rm_items:
transferred_rm_map[item.get('rm_item_code')] = item
for item in pr.get('supplied_items'):
self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
self.assertEquals(se_items, supplied_items)
update_backflush_based_on("BOM")
def test_advance_payment_entry_unlink_against_purchase_order(self):

View File

@@ -1,537 +1,168 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-02-22 01:27:42",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"creation": "2013-02-22 01:27:42",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"main_item_code",
"rm_item_code",
"description",
"batch_no",
"serial_no",
"col_break1",
"required_qty",
"consumed_qty",
"stock_uom",
"rate",
"amount",
"conversion_factor",
"current_stock",
"reference_name",
"bom_detail_no"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "main_item_code",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "main_item_code",
"oldfieldtype": "Data",
"options": "Item",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "main_item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Code",
"oldfieldname": "main_item_code",
"oldfieldtype": "Data",
"options": "Item",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rm_item_code",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Raw Material Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "rm_item_code",
"oldfieldtype": "Data",
"options": "Item",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "rm_item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Raw Material Item Code",
"oldfieldname": "rm_item_code",
"oldfieldtype": "Data",
"options": "Item",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "description",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "300px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"in_global_search": 1,
"in_list_view": 1,
"label": "Description",
"oldfieldname": "description",
"oldfieldtype": "Data",
"print_width": "300px",
"read_only": 1,
"width": "300px"
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "batch_no",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Batch No",
"length": 0,
"no_copy": 1,
"options": "Batch",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"no_copy": 1,
"options": "Batch"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "serial_no",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Serial No",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"no_copy": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "col_break1",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "col_break1",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "required_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Required Qty",
"length": 0,
"no_copy": 0,
"oldfieldname": "required_qty",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "required_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Required Qty",
"oldfieldname": "required_qty",
"oldfieldtype": "Currency",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "consumed_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Consumed Qty",
"length": 0,
"no_copy": 0,
"oldfieldname": "consumed_qty",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "consumed_qty",
"fieldtype": "Float",
"label": "Consumed Qty",
"oldfieldname": "consumed_qty",
"oldfieldtype": "Currency",
"read_only": 1,
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stock_uom",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Stock Uom",
"length": 0,
"no_copy": 0,
"oldfieldname": "stock_uom",
"oldfieldtype": "Data",
"options": "UOM",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock Uom",
"oldfieldname": "stock_uom",
"oldfieldtype": "Data",
"options": "UOM",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rate",
"fieldtype": "Currency",
"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": "Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "rate",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "rate",
"fieldtype": "Currency",
"label": "Rate",
"oldfieldname": "rate",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount",
"fieldtype": "Currency",
"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": "Amount",
"length": 0,
"no_copy": 0,
"oldfieldname": "amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
"oldfieldname": "amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "conversion_factor",
"fieldtype": "Float",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Conversion Factor",
"length": 0,
"no_copy": 0,
"oldfieldname": "conversion_factor",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "conversion_factor",
"fieldtype": "Float",
"hidden": 1,
"label": "Conversion Factor",
"oldfieldname": "conversion_factor",
"oldfieldtype": "Currency",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "current_stock",
"fieldtype": "Float",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Current Stock",
"length": 0,
"no_copy": 0,
"oldfieldname": "current_stock",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "current_stock",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Current Stock",
"oldfieldname": "current_stock",
"oldfieldtype": "Currency",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Reference Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "reference_name",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "reference_name",
"fieldtype": "Data",
"hidden": 1,
"in_list_view": 1,
"label": "Reference Name",
"oldfieldname": "reference_name",
"oldfieldtype": "Data",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bom_detail_no",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "BOM Detail No",
"length": 0,
"no_copy": 0,
"oldfieldname": "bom_detail_no",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldname": "bom_detail_no",
"fieldtype": "Data",
"hidden": 1,
"in_list_view": 1,
"label": "BOM Detail No",
"oldfieldname": "bom_detail_no",
"oldfieldtype": "Data",
"read_only": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-01-07 16:51:59.536291",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Receipt Item Supplied",
"owner": "wasim@webnotestech.com",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"idx": 1,
"istable": 1,
"modified": "2019-11-21 16:25:29.909112",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Receipt Item Supplied",
"owner": "wasim@webnotestech.com",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,33 @@
# Version 12.3.0 Release Notes
### Accounting
1. Statewise GST taxation for India
- Added GST state in the tax category
- Added tax category in the address, sales/purchase tax template
- Based on the address system will fetch the tax template
2. Accounts Payable report based on payment terms
3. Trial Balance Report with filter "Party Name"
4. Fixed asset register report with date filters
### CRM
1. Appointment Scheduling
- Configure the appointment slots using Appointment Booking Settings
- Users can book the appointment through the portal based on slot availability
### HR
1. Refactored Employee Attendance Tool
2. Set allocated amount in employee advance as per total amount
### Fixes
1. Stock entry decimal issue while creating the GL entries
2. Item wise stock balance report
3. Valuation of subcontracting finished good item
4. Not able to create Instructor, Student entries
5. Pricing rule for a product discount
6. POS for serialized items
7. Not able to cancel share transfer entry
8. Ledger entries for compensatory off were not getting created

View File

@@ -46,6 +46,16 @@ def get_data():
"name": "Contract",
"description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"),
},
{
"type": "doctype",
"name": "Appointment",
"description" : _("Helps you manage appointments with your leads"),
},
{
"type": "doctype",
"name": "Newsletter",
"label": _("Newsletter"),
}
]
},
{
@@ -165,6 +175,11 @@ def get_data():
"type": "doctype",
"name": "SMS Settings",
"description": _("Setup SMS gateway settings")
},
{
"type": "doctype",
"label": _("Email Group"),
"name": "Email Group",
}
]
},

View File

@@ -241,6 +241,10 @@ def get_data():
"type": "doctype",
"name": "Quality Inspection Template",
},
{
"type": "doctype",
"name": "Quick Stock Balance",
},
]
},
{

View File

@@ -5,15 +5,17 @@ from __future__ import unicode_literals
import frappe, erpnext
import json
from frappe import _, throw
from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day, nowdate
from erpnext.stock.get_item_details import get_conversion_factor
from frappe.utils import (today, flt, cint, fmt_money, formatdate,
getdate, add_days, add_months, get_last_day, nowdate, get_link_to_form)
from erpnext.stock.get_item_details import get_conversion_factor, get_item_details
from erpnext.setup.utils import get_exchange_rate
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
from erpnext.utilities.transaction_base import TransactionBase
from erpnext.buying.utils import update_last_purchase_rate
from erpnext.controllers.sales_and_purchase_return import validate_return
from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
from erpnext.accounts.doctype.pricing_rule.utils import validate_pricing_rules
from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction,
apply_pricing_rule_for_free_items, get_applied_pricing_rules)
from erpnext.exceptions import InvalidCurrency
from six import text_type
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
@@ -59,7 +61,6 @@ class AccountsController(TransactionBase):
_('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1)
def validate(self):
if not self.get('is_return'):
self.validate_qty_is_not_zero()
@@ -98,10 +99,22 @@ class AccountsController(TransactionBase):
if self.is_return:
self.validate_qty()
else:
self.validate_deferred_start_and_end_date()
validate_regional(self)
if self.doctype != 'Material Request':
validate_pricing_rules(self)
apply_pricing_rule_on_transaction(self)
def validate_deferred_start_and_end_date(self):
for d in self.items:
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
if not (d.service_start_date and d.service_end_date):
frappe.throw(_("Row #{0}: Service Start and End Date is required for deferred accounting").format(d.idx))
elif getdate(d.service_start_date) > getdate(d.service_end_date):
frappe.throw(_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(d.idx))
elif getdate(self.posting_date) > getdate(d.service_end_date):
frappe.throw(_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx))
def validate_invoice_documents_schedule(self):
self.validate_payment_schedule_dates()
@@ -232,7 +245,6 @@ class AccountsController(TransactionBase):
def set_missing_item_details(self, for_validate=False):
"""set missing item values"""
from erpnext.stock.get_item_details import get_item_details
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
if hasattr(self, "items"):
@@ -244,7 +256,6 @@ class AccountsController(TransactionBase):
document_type = "{} Item".format(self.doctype)
parent_dict.update({"document_type": document_type})
self.set('pricing_rules', [])
# party_name field used for customer in quotation
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
parent_dict.update({"customer": parent_dict.get("party_name")})
@@ -264,7 +275,7 @@ class AccountsController(TransactionBase):
if self.get("is_subcontracted"):
args["is_subcontracted"] = self.is_subcontracted
ret = get_item_details(args, self, overwrite_warehouse=False)
ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None:
@@ -285,24 +296,42 @@ class AccountsController(TransactionBase):
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
if ret.get("pricing_rules") and not ret.get("validate_applied_rule", 0):
# if user changed the discount percentage then set user's discount percentage ?
item.set("pricing_rules", ret.get("pricing_rules"))
item.set("discount_percentage", ret.get("discount_percentage"))
item.set("discount_amount", ret.get("discount_amount"))
if ret.get("pricing_rule_for") == "Rate":
item.set("price_list_rate", ret.get("price_list_rate"))
if item.get("price_list_rate"):
item.rate = flt(item.price_list_rate *
(1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
if item.get('discount_amount'):
item.rate = item.price_list_rate - item.discount_amount
if ret.get("pricing_rules"):
self.apply_pricing_rule_on_items(item, ret)
if self.doctype == "Purchase Invoice":
self.set_expense_account(for_validate)
def apply_pricing_rule_on_items(self, item, pricing_rule_args):
if not pricing_rule_args.get("validate_applied_rule", 0):
# if user changed the discount percentage then set user's discount percentage ?
if pricing_rule_args.get("price_or_product_discount") == 'Price':
item.set("pricing_rules", pricing_rule_args.get("pricing_rules"))
item.set("discount_percentage", pricing_rule_args.get("discount_percentage"))
item.set("discount_amount", pricing_rule_args.get("discount_amount"))
if pricing_rule_args.get("pricing_rule_for") == "Rate":
item.set("price_list_rate", pricing_rule_args.get("price_list_rate"))
if item.get("price_list_rate"):
item.rate = flt(item.price_list_rate *
(1.0 - (flt(item.discount_percentage) / 100.0)), item.precision("rate"))
if item.get('discount_amount'):
item.rate = item.price_list_rate - item.discount_amount
elif pricing_rule_args.get('free_item_data'):
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
elif pricing_rule_args.get("validate_applied_rule"):
for pricing_rule in get_applied_pricing_rules(item):
pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule)
for field in ['discount_percentage', 'discount_amount', 'rate']:
if item.get(field) < pricing_rule_doc.get(field):
title = get_link_to_form("Pricing Rule", pricing_rule)
frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
.format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
def set_taxes(self):
if not self.meta.get_field("taxes"):
return
@@ -397,9 +426,10 @@ class AccountsController(TransactionBase):
return gl_dict
def validate_qty_is_not_zero(self):
for item in self.items:
if not item.qty:
frappe.throw(_("Item quantity can not be zero"))
if self.doctype != "Purchase Receipt":
for item in self.items:
if not item.qty:
frappe.throw(_("Item quantity can not be zero"))
def validate_account_currency(self, account, account_currency=None):
valid_currency = [self.company_currency]

View File

@@ -221,7 +221,7 @@ class BuyingController(StockController):
"backflush_raw_materials_of_subcontract_based_on")
if (self.doctype == 'Purchase Receipt' and
backflush_raw_materials_based_on != 'BOM'):
self.update_raw_materials_supplied_based_on_stock_entries(raw_material_table)
self.update_raw_materials_supplied_based_on_stock_entries()
else:
for item in self.get("items"):
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
@@ -241,41 +241,96 @@ class BuyingController(StockController):
if self.is_subcontracted == "No" and self.get("supplied_items"):
self.set('supplied_items', [])
def update_raw_materials_supplied_based_on_stock_entries(self, raw_material_table):
self.set(raw_material_table, [])
purchase_orders = [d.purchase_order for d in self.items]
if purchase_orders:
items = get_subcontracted_raw_materials_from_se(purchase_orders)
backflushed_raw_materials = get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, self.name)
def update_raw_materials_supplied_based_on_stock_entries(self):
self.set('supplied_items', [])
for d in items:
qty = d.qty - backflushed_raw_materials.get(d.item_code, 0)
rm = self.append(raw_material_table, {})
rm.rm_item_code = d.item_code
rm.item_name = d.item_name
rm.main_item_code = d.main_item_code
rm.description = d.description
rm.stock_uom = d.stock_uom
rm.required_qty = qty
rm.consumed_qty = qty
rm.serial_no = d.serial_no
rm.batch_no = d.batch_no
purchase_orders = set([d.purchase_order for d in self.items])
# get raw materials rate
from erpnext.stock.utils import get_incoming_rate
rm.rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * qty,
"serial_no": rm.serial_no
})
if not rm.rate:
rm.rate = get_valuation_rate(d.item_code, self.supplier_warehouse,
self.doctype, self.name, currency=self.company_currency, company = self.company)
# qty of raw materials backflushed (for each item per purchase order)
backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders)
rm.amount = qty * flt(rm.rate)
# qty of "finished good" item yet to be received
qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
for item in self.get('items'):
# reset raw_material cost
item.rm_supp_cost = 0
# qty of raw materials transferred to the supplier
transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code)
non_stock_items = get_non_stock_items(item.purchase_order, item.item_code)
item_key = '{}{}'.format(item.item_code, item.purchase_order)
fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
for raw_material in transferred_raw_materials + non_stock_items:
rm_item_key = '{}{}'.format(raw_material.rm_item_code, item.purchase_order)
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
consumed_qty = raw_material_data.get('qty', 0)
consumed_serial_nos = raw_material_data.get('serial_nos', '')
consumed_batch_nos = raw_material_data.get('batch_nos', '')
transferred_qty = raw_material.qty
rm_qty_to_be_consumed = transferred_qty - consumed_qty
# backflush all remaining transferred qty in the last Purchase Receipt
if fg_yet_to_be_received == item.qty:
qty = rm_qty_to_be_consumed
else:
qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty
if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'):
qty = frappe.utils.ceil(qty)
if qty > rm_qty_to_be_consumed:
qty = rm_qty_to_be_consumed
if not qty: continue
if raw_material.serial_nos:
set_serial_nos(raw_material, consumed_serial_nos, qty)
if raw_material.batch_nos:
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
qty, transferred_batch_qty_map, backflushed_batch_qty_map)
for batch_data in batches_qty:
qty = batch_data['qty']
raw_material.batch_no = batch_data['batch']
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
else:
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty):
rm = self.append('supplied_items', {})
rm.update(raw_material_data)
rm.required_qty = qty
rm.consumed_qty = qty
if not raw_material_data.get('non_stock_item'):
from erpnext.stock.utils import get_incoming_rate
rm.rate = get_incoming_rate({
"item_code": raw_material_data.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * qty,
"serial_no": rm.serial_no
})
if not rm.rate:
rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse,
self.doctype, self.name, currency=self.company_currency, company=self.company)
rm.amount = qty * flt(rm.rate)
fg_item_doc.rm_supp_cost += rm.amount
def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
exploded_item = 1
@@ -387,9 +442,11 @@ class BuyingController(StockController):
item_codes = list(set(item.item_code for item in
self.get("items")))
if item_codes:
self._sub_contracted_items = [r[0] for r in frappe.db.sql("""select name
from `tabItem` where name in (%s) and is_sub_contracted_item=1""" % \
(", ".join((["%s"]*len(item_codes))),), item_codes)]
items = frappe.get_all('Item', filters={
'name': ['in', item_codes],
'is_sub_contracted_item': 1
})
self._sub_contracted_items = [item.name for item in items]
return self._sub_contracted_items
@@ -722,28 +779,72 @@ def get_items_from_bom(item_code, bom, exploded_item=1):
return bom_items
def get_subcontracted_raw_materials_from_se(purchase_orders):
return frappe.db.sql("""
select
sed.item_name, sed.item_code, sum(sed.qty) as qty, sed.description,
sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no
from `tabStock Entry` se,`tabStock Entry Detail` sed
where
se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor'
and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != ''
group by sed.item_code, sed.t_warehouse
""" % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1)
def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
common_query = """
SELECT
sed.item_code AS rm_item_code,
SUM(sed.qty) AS qty,
sed.description,
sed.stock_uom,
sed.subcontracted_item AS main_item_code,
{serial_no_concat_syntax} AS serial_nos,
{batch_no_concat_syntax} AS batch_nos
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
WHERE
se.name = sed.parent
AND se.docstatus=1
AND se.purpose='Send to Subcontractor'
AND se.purchase_order = %s
AND IFNULL(sed.t_warehouse, '') != ''
AND sed.subcontracted_item = %s
GROUP BY sed.item_code, sed.subcontracted_item
"""
raw_materials = frappe.db.multisql({
'mariadb': common_query.format(
serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)",
batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)"
),
'postgres': common_query.format(
serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')",
batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')"
)
}, (purchase_order, fg_item), as_dict=1)
def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchase_receipt):
return frappe._dict(frappe.db.sql("""
select
prsi.rm_item_code as item_code, sum(prsi.consumed_qty) as qty
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
where
pr.name = pri.parent and pr.name = prsi.parent and pri.purchase_order in (%s)
and pri.item_code = prsi.main_item_code and pr.name != '%s' and pr.docstatus = 1
group by prsi.rm_item_code
""" % (','.join(['%s'] * len(purchase_orders)), purchase_receipt), tuple(purchase_orders)))
return raw_materials
def get_backflushed_subcontracted_raw_materials(purchase_orders):
common_query = """
SELECT
CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key,
SUM(prsi.consumed_qty) AS qty,
{serial_no_concat_syntax} AS serial_nos,
{batch_no_concat_syntax} AS batch_nos
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
WHERE
pr.name = pri.parent
AND pr.name = prsi.parent
AND pri.purchase_order IN %s
AND pri.item_code = prsi.main_item_code
AND pr.docstatus = 1
GROUP BY prsi.rm_item_code, pri.purchase_order
"""
backflushed_raw_materials = frappe.db.multisql({
'mariadb': common_query.format(
serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)",
batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)"
),
'postgres': common_query.format(
serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')",
batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')"
)
}, (purchase_orders, ), as_dict=1)
backflushed_raw_materials_map = frappe._dict()
for item in backflushed_raw_materials:
backflushed_raw_materials_map.setdefault(item.item_key, item)
return backflushed_raw_materials_map
def get_asset_item_details(asset_items):
asset_items_data = {}
@@ -776,3 +877,125 @@ def validate_item_type(doc, fieldname, message):
error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message))
frappe.throw(error_message)
def get_qty_to_be_received(purchase_orders):
return frappe._dict(frappe.db.sql("""
SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key,
SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received
FROM `tabPurchase Order Item` poi
WHERE
poi.`parent` in %s
GROUP BY poi.`item_code`, poi.`parent`
HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`)
""", (purchase_orders)))
def get_non_stock_items(purchase_order, fg_item_code):
return frappe.db.sql("""
SELECT
pois.main_item_code,
pois.rm_item_code,
item.description,
pois.required_qty AS qty,
pois.rate,
1 as non_stock_item,
pois.stock_uom
FROM `tabPurchase Order Item Supplied` pois, `tabItem` item
WHERE
pois.`rm_item_code` = item.`name`
AND item.is_stock_item = 0
AND pois.`parent` = %s
AND pois.`main_item_code` = %s
""", (purchase_order, fg_item_code), as_dict=1)
def set_serial_nos(raw_material, consumed_serial_nos, qty):
serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \
set(get_serial_nos(consumed_serial_nos))
if serial_nos and qty <= len(serial_nos):
raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)])
def get_transferred_batch_qty_map(purchase_order, fg_item):
# returns
# {
# (item_code, fg_code): {
# batch1: 10, # qty
# batch2: 16
# },
# }
transferred_batch_qty_map = {}
transferred_batches = frappe.db.sql("""
SELECT
sed.batch_no,
SUM(sed.qty) AS qty,
sed.item_code
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
WHERE
se.name = sed.parent
AND se.docstatus=1
AND se.purpose='Send to Subcontractor'
AND se.purchase_order = %s
AND sed.subcontracted_item = %s
AND sed.batch_no IS NOT NULL
GROUP BY
sed.batch_no,
sed.item_code
""", (purchase_order, fg_item), as_dict=1)
for batch_data in transferred_batches:
transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
return transferred_batch_qty_map
def get_backflushed_batch_qty_map(purchase_order, fg_item):
# returns
# {
# (item_code, fg_code): {
# batch1: 10, # qty
# batch2: 16
# },
# }
backflushed_batch_qty_map = {}
backflushed_batches = frappe.db.sql("""
SELECT
pris.batch_no,
SUM(pris.consumed_qty) AS qty,
pris.rm_item_code AS item_code
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris
WHERE
pr.name = pri.parent
AND pri.parent = pris.parent
AND pri.purchase_order = %s
AND pri.item_code = pris.main_item_code
AND pr.docstatus = 1
AND pris.main_item_code = %s
AND pris.batch_no IS NOT NULL
GROUP BY
pris.rm_item_code, pris.batch_no
""", (purchase_order, fg_item), as_dict=1)
for batch_data in backflushed_batches:
backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
return backflushed_batch_qty_map
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map):
# Returns available batches to be backflushed based on requirements
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
available_batches = []
for (batch, transferred_qty) in transferred_batches.items():
backflushed_qty = backflushed_batches.get(batch, 0)
available_qty = transferred_qty - backflushed_qty
if available_qty >= required_qty:
available_batches.append({'batch': batch, 'qty': required_qty})
break
else:
available_batches.append({'batch': batch, 'qty': available_qty})
required_qty -= available_qty
return available_batches

View File

@@ -552,7 +552,7 @@ class calculate_taxes_and_totals(object):
if item.price_list_rate:
if item.pricing_rules and not self.doc.ignore_pricing_rule:
for d in item.pricing_rules.split(','):
pricing_rule = frappe.get_doc('Pricing Rule', d)
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
or (pricing_rule.margin_type == 'Percentage'):

View File

@@ -0,0 +1,17 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Appointment', {
refresh: function(frm) {
if(frm.doc.lead){
frm.add_custom_button(frm.doc.lead,()=>{
frappe.set_route("Form", "Lead", frm.doc.lead);
});
}
if(frm.doc.calendar_event){
frm.add_custom_button(__(frm.doc.calendar_event),()=>{
frappe.set_route("Form", "Event", frm.doc.calendar_event);
});
}
}
});

View File

@@ -0,0 +1,153 @@
{
"autoname": "format:APMT-{customer_name}-{####}",
"creation": "2019-08-27 10:48:27.926283",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"scheduled_time",
"status",
"customer_details_section",
"customer_name",
"customer_phone_number",
"customer_skype",
"customer_email",
"col_br_2",
"customer_details",
"linked_docs_section",
"lead",
"col_br_3",
"calendar_event"
],
"fields": [
{
"fieldname": "customer_details_section",
"fieldtype": "Section Break",
"label": "Customer Details"
},
{
"fieldname": "customer_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Name",
"reqd": 1
},
{
"fieldname": "customer_phone_number",
"fieldtype": "Data",
"label": "Phone Number"
},
{
"fieldname": "customer_skype",
"fieldtype": "Data",
"label": "Skype ID"
},
{
"fieldname": "customer_details",
"fieldtype": "Long Text",
"label": "Details"
},
{
"fieldname": "scheduled_time",
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Scheduled Time",
"reqd": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Open\nUnverified\nClosed",
"reqd": 1
},
{
"fieldname": "lead",
"fieldtype": "Link",
"label": "Lead",
"options": "Lead"
},
{
"fieldname": "calendar_event",
"fieldtype": "Link",
"label": "Calendar Event",
"options": "Event"
},
{
"fieldname": "col_br_2",
"fieldtype": "Column Break"
},
{
"fieldname": "customer_email",
"fieldtype": "Data",
"label": "Email",
"reqd": 1
},
{
"fieldname": "linked_docs_section",
"fieldtype": "Section Break",
"label": "Linked Documents"
},
{
"fieldname": "col_br_3",
"fieldtype": "Column Break"
}
],
"modified": "2019-10-14 15:23:54.630731",
"modified_by": "Administrator",
"module": "CRM",
"name": "Appointment",
"name_case": "UPPER CASE",
"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,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Guest",
"share": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,223 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import urllib
from collections import Counter
from datetime import timedelta
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_url
from frappe.utils.verified_command import verify_request, get_signed_params
class Appointment(Document):
def find_lead_by_email(self):
lead_list = frappe.get_list(
'Lead', filters={'email_id': self.customer_email}, ignore_permissions=True)
if lead_list:
return lead_list[0].name
return None
def before_insert(self):
number_of_appointments_in_same_slot = frappe.db.count(
'Appointment', filters={'scheduled_time': self.scheduled_time})
number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents')
if not number_of_agents == 0:
if (number_of_appointments_in_same_slot >= number_of_agents):
frappe.throw('Time slot is not available')
# Link lead
if not self.lead:
self.lead = self.find_lead_by_email()
def after_insert(self):
if self.lead:
# Create Calendar event
self.auto_assign()
self.create_calendar_event()
else:
# Set status to unverified
self.status = 'Unverified'
# Send email to confirm
self.send_confirmation_email()
def send_confirmation_email(self):
verify_url = self._get_verify_url()
template = 'confirm_appointment'
args = {
"link":verify_url,
"site_url":frappe.utils.get_url(),
"full_name":self.customer_name,
}
frappe.sendmail(recipients=[self.customer_email],
template=template,
args=args,
subject=_('Appointment Confirmation'))
if frappe.session.user == "Guest":
frappe.msgprint(
'Please check your email to confirm the appointment')
else :
frappe.msgprint(
'Appointment was created. But no lead was found. Please check the email to confirm')
def on_change(self):
# Sync Calendar
if not self.calendar_event:
return
cal_event = frappe.get_doc('Event', self.calendar_event)
cal_event.starts_on = self.scheduled_time
cal_event.save(ignore_permissions=True)
def set_verified(self, email):
if not email == self.customer_email:
frappe.throw('Email verification failed.')
# Create new lead
self.create_lead_and_link()
# Remove unverified status
self.status = 'Open'
# Create calender event
self.auto_assign()
self.create_calendar_event()
self.save(ignore_permissions=True)
frappe.db.commit()
def create_lead_and_link(self):
# Return if already linked
if self.lead:
return
lead = frappe.get_doc({
'doctype': 'Lead',
'lead_name': self.customer_name,
'email_id': self.customer_email,
'notes': self.customer_details,
'phone': self.customer_phone_number,
})
lead.insert(ignore_permissions=True)
# Link lead
self.lead = lead.name
def auto_assign(self):
from frappe.desk.form.assign_to import add as add_assignemnt
existing_assignee = self.get_assignee_from_latest_opportunity()
if existing_assignee:
# If the latest opportunity is assigned to someone
# Assign the appointment to the same
add_assignemnt({
'doctype': self.doctype,
'name': self.name,
'assign_to': existing_assignee
})
return
if self._assign:
return
available_agents = _get_agents_sorted_by_asc_workload(
self.scheduled_time.date())
for agent in available_agents:
if(_check_agent_availability(agent, self.scheduled_time)):
agent = agent[0]
add_assignemnt({
'doctype': self.doctype,
'name': self.name,
'assign_to': agent
})
break
def get_assignee_from_latest_opportunity(self):
if not self.lead:
return None
if not frappe.db.exists('Lead', self.lead):
return None
opporutnities = frappe.get_list(
'Opportunity',
filters={
'party_name': self.lead,
},
ignore_permissions=True,
order_by='creation desc')
if not opporutnities:
return None
latest_opportunity = frappe.get_doc('Opportunity', opporutnities[0].name )
assignee = latest_opportunity._assign
if not assignee:
return None
assignee = frappe.parse_json(assignee)[0]
return assignee
def create_calendar_event(self):
if self.calendar_event:
return
appointment_event = frappe.get_doc({
'doctype': 'Event',
'subject': ' '.join(['Appointment with', self.customer_name]),
'starts_on': self.scheduled_time,
'status': 'Open',
'type': 'Public',
'send_reminder': frappe.db.get_single_value('Appointment Booking Settings', 'email_reminders'),
'event_participants': [dict(reference_doctype='Lead', reference_docname=self.lead)]
})
employee = _get_employee_from_user(self._assign)
if employee:
appointment_event.append('event_participants', dict(
reference_doctype='Employee',
reference_docname=employee.name))
appointment_event.insert(ignore_permissions=True)
self.calendar_event = appointment_event.name
self.save(ignore_permissions=True)
def _get_verify_url(self):
verify_route = '/book_appointment/verify'
params = {
'email': self.customer_email,
'appointment': self.name
}
return get_url(verify_route + '?' + get_signed_params(params))
def _get_agents_sorted_by_asc_workload(date):
appointments = frappe.db.get_list('Appointment', fields='*')
agent_list = _get_agent_list_as_strings()
if not appointments:
return agent_list
appointment_counter = Counter(agent_list)
for appointment in appointments:
assigned_to = frappe.parse_json(appointment._assign)
if not assigned_to:
continue
if (assigned_to[0] in agent_list) and appointment.scheduled_time.date() == date:
appointment_counter[assigned_to[0]] += 1
sorted_agent_list = appointment_counter.most_common()
sorted_agent_list.reverse()
return sorted_agent_list
def _get_agent_list_as_strings():
agent_list_as_strings = []
agent_list = frappe.get_doc('Appointment Booking Settings').agent_list
for agent in agent_list:
agent_list_as_strings.append(agent.user)
return agent_list_as_strings
def _check_agent_availability(agent_email, scheduled_time):
appointemnts_at_scheduled_time = frappe.get_list(
'Appointment', filters={'scheduled_time': scheduled_time})
for appointment in appointemnts_at_scheduled_time:
if appointment._assign == agent_email:
return False
return True
def _get_employee_from_user(user):
employee_docname = frappe.db.exists(
{'doctype': 'Employee', 'user_id': user})
if employee_docname:
# frappe.db.exists returns a tuple of a tuple
return frappe.get_doc('Employee', employee_docname[0][0])
return None

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
import datetime
def create_test_lead():
test_lead = frappe.db.exists({'doctype': 'Lead', 'lead_name': 'Test Lead'})
if test_lead:
return frappe.get_doc('Lead', test_lead[0][0])
test_lead = frappe.get_doc({
'doctype': 'Lead',
'lead_name': 'Test Lead',
'email_id': 'test@example.com'
})
test_lead.insert(ignore_permissions=True)
return test_lead
def create_test_appointments():
test_appointment = frappe.db.exists(
{'doctype': 'Appointment', 'scheduled_time':datetime.datetime.now(),'email':'test@example.com'})
if test_appointment:
return frappe.get_doc('Appointment', test_appointment[0][0])
test_appointment = frappe.get_doc({
'doctype': 'Appointment',
'email': 'test@example.com',
'status': 'Open',
'customer_name': 'Test Lead',
'customer_phone_number': '666',
'customer_skype': 'test',
'customer_email': 'test@example.com',
'scheduled_time': datetime.datetime.now()
})
test_appointment.insert()
return test_appointment
class TestAppointment(unittest.TestCase):
test_appointment = test_lead = None
def setUp(self):
self.test_lead = create_test_lead()
self.test_appointment = create_test_appointments()
def test_calendar_event_created(self):
cal_event = frappe.get_doc(
'Event', self.test_appointment.calendar_event)
self.assertEqual(cal_event.starts_on,
self.test_appointment.scheduled_time)
def test_lead_linked(self):
lead = frappe.get_doc('Lead', self.test_lead.name)
self.assertIsNotNone(lead)

View File

@@ -0,0 +1,10 @@
frappe.ui.form.on('Appointment Booking Settings', 'validate',check_times);
function check_times(frm) {
$.each(frm.doc.availability_of_slots || [], function (i, d) {
let from_time = Date.parse('01/01/2019 ' + d.from_time);
let to_time = Date.parse('01/01/2019 ' + d.to_time);
if (from_time > to_time) {
frappe.throw(__(`In row ${i + 1} of Appointment Booking Slots : "To Time" must be later than "From Time"`));
}
});
}

View File

@@ -0,0 +1,151 @@
{
"creation": "2019-08-27 10:56:48.309824",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"enable_scheduling",
"agent_detail_section",
"availability_of_slots",
"number_of_agents",
"agent_list",
"holiday_list",
"appointment_details_section",
"appointment_duration",
"email_reminders",
"advance_booking_days",
"success_details",
"success_redirect_url"
],
"fields": [
{
"fieldname": "availability_of_slots",
"fieldtype": "Table",
"label": "Availability Of Slots",
"options": "Appointment Booking Slots",
"reqd": 1
},
{
"default": "1",
"fieldname": "number_of_agents",
"fieldtype": "Int",
"hidden": 1,
"in_list_view": 1,
"label": "Number of Concurrent Appointments",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "holiday_list",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Holiday List",
"options": "Holiday List",
"reqd": 1
},
{
"default": "60",
"fieldname": "appointment_duration",
"fieldtype": "Int",
"label": "Appointment Duration (In Minutes)",
"reqd": 1
},
{
"default": "0",
"description": "Notify customer and agent via email on the day of the appointment.",
"fieldname": "email_reminders",
"fieldtype": "Check",
"label": "Notify Via Email"
},
{
"default": "7",
"fieldname": "advance_booking_days",
"fieldtype": "Int",
"label": "Number of days appointments can be booked in advance",
"reqd": 1
},
{
"fieldname": "agent_list",
"fieldtype": "Table MultiSelect",
"label": "Agents",
"options": "Assignment Rule User",
"reqd": 1
},
{
"default": "0",
"fieldname": "enable_scheduling",
"fieldtype": "Check",
"label": "Enable Appointment Scheduling",
"reqd": 1
},
{
"fieldname": "agent_detail_section",
"fieldtype": "Section Break",
"label": "Agent Details"
},
{
"fieldname": "appointment_details_section",
"fieldtype": "Section Break",
"label": "Appointment Details"
},
{
"fieldname": "success_details",
"fieldtype": "Section Break",
"label": "Success Settings"
},
{
"description": "Leave blank for home.\nThis is relative to site URL, for example \"about\" will redirect to \"https://yoursitename.com/about\"",
"fieldname": "success_redirect_url",
"fieldtype": "Data",
"label": "Success Redirect URL"
}
],
"issingle": 1,
"modified": "2019-11-26 12:14:17.669366",
"modified_by": "Administrator",
"module": "CRM",
"name": "Appointment Booking Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Guest",
"share": 1
},
{
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "HR Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Sales Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
import datetime
from frappe.model.document import Document
class AppointmentBookingSettings(Document):
agent_list = [] #Hack
min_date = '01/01/1970 '
format_string = "%d/%m/%Y %H:%M:%S"
def validate(self):
self.validate_availability_of_slots()
def save(self):
self.number_of_agents = len(self.agent_list)
super(AppointmentBookingSettings, self).save()
def validate_availability_of_slots(self):
for record in self.availability_of_slots:
from_time = datetime.datetime.strptime(
self.min_date+record.from_time, self.format_string)
to_time = datetime.datetime.strptime(
self.min_date+record.to_time, self.format_string)
timedelta = to_time-from_time
self.validate_from_and_to_time(from_time, to_time)
self.duration_is_divisible(from_time, to_time)
def validate_from_and_to_time(self, from_time, to_time):
if from_time > to_time:
err_msg = _('<b>From Time</b> cannot be later than <b>To Time</b> for {0}').format(record.day_of_week)
frappe.throw(_(err_msg))
def duration_is_divisible(self, from_time, to_time):
timedelta = to_time - from_time
if timedelta.total_seconds() % (self.appointment_duration * 60):
frappe.throw(
_('The difference between from time and To Time must be a multiple of Appointment'))

View File

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

View File

@@ -0,0 +1,46 @@
{
"creation": "2019-11-19 10:49:49.494927",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"day_of_week",
"from_time",
"to_time"
],
"fields": [
{
"fieldname": "day_of_week",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Day Of Week",
"options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
"reqd": 1
},
{
"fieldname": "from_time",
"fieldtype": "Time",
"in_list_view": 1,
"label": "From Time ",
"reqd": 1
},
{
"fieldname": "to_time",
"fieldtype": "Time",
"in_list_view": 1,
"label": "To Time",
"reqd": 1
}
],
"istable": 1,
"modified": "2019-11-19 10:49:49.494927",
"modified_by": "Administrator",
"module": "CRM",
"name": "Appointment Booking Slots",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@@ -0,0 +1,46 @@
{
"creation": "2019-09-10 15:02:05.779434",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"day_of_week",
"from_time",
"to_time"
],
"fields": [
{
"fieldname": "day_of_week",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Day Of Week",
"options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
"reqd": 1
},
{
"fieldname": "from_time",
"fieldtype": "Time",
"in_list_view": 1,
"label": "From Time",
"reqd": 1
},
{
"fieldname": "to_time",
"fieldtype": "Time",
"in_list_view": 1,
"label": "To Time",
"reqd": 1
}
],
"istable": 1,
"modified": "2019-09-10 15:05:20.406855",
"modified_by": "Administrator",
"module": "CRM",
"name": "Availability Of Slots",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@@ -41,7 +41,8 @@ class EmailCampaign(Document):
email_campaign_exists = frappe.db.exists("Email Campaign", {
"campaign_name": self.campaign_name,
"recipient": self.recipient,
"status": ("in", ["In Progress", "Scheduled"])
"status": ("in", ["In Progress", "Scheduled"]),
"name": ("!=", self.name)
})
if email_campaign_exists:
frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient))
@@ -78,7 +79,7 @@ def send_mail(entry, email_campaign):
comm = make(
doctype = "Email Campaign",
name = email_campaign.name,
subject = email_template.get("subject"),
subject = frappe.render_template(email_template.get("subject"), context),
content = frappe.render_template(email_template.get("response"), context),
sender = sender,
recipients = recipient,

File diff suppressed because it is too large Load Diff

View File

@@ -130,10 +130,11 @@ class Opportunity(TransactionBase):
def has_lost_quotation(self):
lost_quotation = frappe.db.sql("""
select q.name
from `tabQuotation` q, `tabQuotation Item` qi
where q.name = qi.parent and q.docstatus=1
and qi.prevdoc_docname =%s and q.status = 'Lost'
select name
from `tabQuotation`
where docstatus=1
and opportunity =%s
and status = 'Lost'
""", self.name)
if lost_quotation:
if self.has_active_quotation():

View File

@@ -4,11 +4,11 @@ cur_frm.add_fetch("employee", "image", "image");
frappe.ui.form.on("Instructor", {
employee: function(frm) {
if(!frm.doc.employee) return;
frappe.db.get_value('Employee', {name: frm.doc.employee}, 'company', (company) => {
frappe.db.get_value('Employee', {name: frm.doc.employee}, 'company', (d) => {
frm.set_query("department", function() {
return {
"filters": {
"company": company,
"company": d.company,
}
};
});
@@ -16,7 +16,7 @@ frappe.ui.form.on("Instructor", {
frm.set_query("department", "instructor_log", function() {
return {
"filters": {
"company": company,
"company": d.company,
}
};
});

View File

@@ -40,7 +40,7 @@ class Student(Document):
frappe.throw(_("Student {0} exist against student applicant {1}").format(student[0][0], self.student_applicant))
def after_insert(self):
if not frappe.get_single('Education Settings').user_creation_skip:
if not frappe.get_single('Education Settings').get('user_creation_skip'):
self.create_student_user()
def create_student_user(self):

View File

@@ -182,6 +182,7 @@ standard_portal_menu_items = [
{"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"},
{"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application", "role": "Non Profit Portal User"},
{"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"},
{"title": _("Appointment Booking"), "route": "/book_appointment"},
]
default_roles = [
@@ -248,10 +249,10 @@ doc_events = {
"on_trash": "erpnext.regional.check_deletion_permission"
},
'Address': {
'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code']
'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category']
},
('Sales Invoice', 'Purchase Invoice', 'Delivery Note'): {
'validate': 'erpnext.regional.india.utils.set_place_of_supply'
('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
'validate': ['erpnext.regional.india.utils.set_place_of_supply']
},
"Contact": {
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
@@ -301,7 +302,8 @@ scheduler_events = {
"erpnext.quality_management.doctype.quality_review.quality_review.review",
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status"
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
"erpnext.selling.doctype.quotation.quotation.set_expired_status"
],
"daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send",

View File

@@ -19,4 +19,4 @@ frappe.ui.form.on('Compensatory Leave Request', {
frm.set_df_property('half_day_date', 'reqd', false);
}
}
});
});

View File

@@ -5,9 +5,10 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import date_diff, add_days, getdate
from frappe.utils import date_diff, add_days, getdate, cint
from frappe.model.document import Document
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, get_holidays_for_employee
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
get_holidays_for_employee, create_additional_leave_ledger_entry
class CompensatoryLeaveRequest(Document):
@@ -25,16 +26,14 @@ class CompensatoryLeaveRequest(Document):
frappe.throw(_("Leave Type is madatory"))
def validate_attendance(self):
query = """select attendance_date, status
from `tabAttendance` where
attendance_date between %(work_from_date)s and %(work_end_date)s
and docstatus=1 and status = 'Present' and employee=%(employee)s"""
attendance = frappe.get_all('Attendance',
filters={
'attendance_date': ['between', (self.work_from_date, self.work_end_date)],
'status': 'Present',
'docstatus': 1,
'employee': self.employee
}, fields=['attendance_date', 'status'])
attendance = frappe.db.sql(query, {
"work_from_date": self.work_from_date,
"work_end_date": self.work_end_date,
"employee": self.employee
}, as_dict=True)
if len(attendance) < date_diff(self.work_end_date, self.work_from_date) + 1:
frappe.throw(_("You are not present all day(s) between compensatory leave request days"))
@@ -50,13 +49,19 @@ class CompensatoryLeaveRequest(Document):
date_difference -= 0.5
leave_period = get_leave_period(self.work_from_date, self.work_end_date, company)
if leave_period:
leave_allocation = self.exists_allocation_for_period(leave_period)
leave_allocation = self.get_existing_allocation_for_period(leave_period)
if leave_allocation:
leave_allocation.new_leaves_allocated += date_difference
leave_allocation.submit()
leave_allocation.validate()
leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated)
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
# generate additional ledger entry for the new compensatory leaves off
create_additional_leave_ledger_entry(leave_allocation, date_difference, add_days(self.work_end_date, 1))
else:
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
self.db_set("leave_allocation", leave_allocation.name)
self.leave_allocation=leave_allocation.name
else:
frappe.throw(_("There is no leave period in between {0} and {1}").format(self.work_from_date, self.work_end_date))
@@ -68,11 +73,16 @@ class CompensatoryLeaveRequest(Document):
leave_allocation = frappe.get_doc("Leave Allocation", self.leave_allocation)
if leave_allocation:
leave_allocation.new_leaves_allocated -= date_difference
if leave_allocation.total_leaves_allocated - date_difference <= 0:
leave_allocation.total_leaves_allocated = 0
leave_allocation.submit()
if leave_allocation.new_leaves_allocated - date_difference <= 0:
leave_allocation.new_leaves_allocated = 0
leave_allocation.validate()
leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated)
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
def exists_allocation_for_period(self, leave_period):
# create reverse entry on cancelation
create_additional_leave_ledger_entry(leave_allocation, date_difference * -1, add_days(self.work_end_date, 1))
def get_existing_allocation_for_period(self, leave_period):
leave_allocation = frappe.db.sql("""
select name
from `tabLeave Allocation`
@@ -95,17 +105,18 @@ class CompensatoryLeaveRequest(Document):
def create_leave_allocation(self, leave_period, date_difference):
is_carry_forward = frappe.db.get_value("Leave Type", self.leave_type, "is_carry_forward")
allocation = frappe.new_doc("Leave Allocation")
allocation.employee = self.employee
allocation.employee_name = self.employee_name
allocation.leave_type = self.leave_type
allocation.from_date = add_days(self.work_end_date, 1)
allocation.to_date = leave_period[0].to_date
allocation.new_leaves_allocated = date_difference
allocation.total_leaves_allocated = date_difference
allocation.description = self.reason
if is_carry_forward == 1:
allocation.carry_forward = True
allocation.save(ignore_permissions = True)
allocation = frappe.get_doc(dict(
doctype="Leave Allocation",
employee=self.employee,
employee_name=self.employee_name,
leave_type=self.leave_type,
from_date=add_days(self.work_end_date, 1),
to_date=leave_period[0].to_date,
carry_forward=cint(is_carry_forward),
new_leaves_allocated=date_difference,
total_leaves_allocated=date_difference,
description=self.reason
))
allocation.insert(ignore_permissions=True)
allocation.submit()
return allocation
return allocation

View File

@@ -5,37 +5,128 @@ from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils import today, add_months, add_days
from erpnext.hr.doctype.attendance_request.test_attendance_request import get_employee
from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
# class TestCompensatoryLeaveRequest(unittest.TestCase):
# def get_compensatory_leave_request(self):
# return frappe.get_doc('Compensatory Leave Request', dict(
# employee = employee,
# work_from_date = today,
# work_to_date = today,
# reason = 'test'
# )).insert()
#
# def test_creation_of_leave_allocation(self):
# employee = get_employee()
# today = get_today()
#
# compensatory_leave_request = self.get_compensatory_leave_request(today)
#
# before = get_leave_balance(employee, compensatory_leave_request.leave_type)
#
# compensatory_leave_request.submit()
#
# self.assertEqual(get_leave_balance(employee, compensatory_leave_request.leave_type), before + 1)
#
# def test_max_compensatory_leave(self):
# employee = get_employee()
# today = get_today()
#
# compensatory_leave_request = self.get_compensatory_leave_request()
#
# frappe.db.set_value('Leave Type', compensatory_leave_request.leave_type, 'max_leaves_allowed', 0)
#
# self.assertRaises(MaxLeavesLimitCrossed, compensatory_leave_request.submit)
#
# frappe.db.set_value('Leave Type', compensatory_leave_request.leave_type, 'max_leaves_allowed', 10)
#
class TestCompensatoryLeaveRequest(unittest.TestCase):
def setUp(self):
frappe.db.sql(''' delete from `tabCompensatory Leave Request`''')
frappe.db.sql(''' delete from `tabLeave Ledger Entry`''')
frappe.db.sql(''' delete from `tabLeave Allocation`''')
frappe.db.sql(''' delete from `tabAttendance` where attendance_date in {0} '''.format((today(), add_days(today(), -1)))) #nosec
create_leave_period(add_months(today(), -3), add_months(today(), 3), "_Test Company")
create_holiday_list()
employee = get_employee()
employee.holiday_list = "_Test Compensatory Leave"
employee.save()
def test_leave_balance_on_submit(self):
''' check creation of leave allocation on submission of compensatory leave request '''
employee = get_employee()
mark_attendance(employee)
compensatory_leave_request = get_compensatory_leave_request(employee.name)
before = get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, today())
compensatory_leave_request.submit()
self.assertEqual(get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, add_days(today(), 1)), before + 1)
def test_leave_allocation_update_on_submit(self):
employee = get_employee()
mark_attendance(employee, date=add_days(today(), -1))
compensatory_leave_request = get_compensatory_leave_request(employee.name, leave_date=add_days(today(), -1))
compensatory_leave_request.submit()
# leave allocation creation on submit
leaves_allocated = frappe.db.get_value('Leave Allocation', {
'name': compensatory_leave_request.leave_allocation
}, ['total_leaves_allocated'])
self.assertEqual(leaves_allocated, 1)
mark_attendance(employee)
compensatory_leave_request = get_compensatory_leave_request(employee.name)
compensatory_leave_request.submit()
# leave allocation updates on submission of second compensatory leave request
leaves_allocated = frappe.db.get_value('Leave Allocation', {
'name': compensatory_leave_request.leave_allocation
}, ['total_leaves_allocated'])
self.assertEqual(leaves_allocated, 2)
def test_creation_of_leave_ledger_entry_on_submit(self):
''' check creation of leave ledger entry on submission of leave request '''
employee = get_employee()
mark_attendance(employee)
compensatory_leave_request = get_compensatory_leave_request(employee.name)
compensatory_leave_request.submit()
filters = dict(transaction_name=compensatory_leave_request.leave_allocation)
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters)
self.assertEquals(len(leave_ledger_entry), 1)
self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
self.assertEquals(leave_ledger_entry[0].leaves, 1)
# check reverse leave ledger entry on cancellation
compensatory_leave_request.cancel()
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc')
self.assertEquals(len(leave_ledger_entry), 2)
self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
self.assertEquals(leave_ledger_entry[0].leaves, -1)
def get_compensatory_leave_request(employee, leave_date=today()):
prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request',
dict(leave_type='Compensatory Off',
work_from_date=leave_date,
work_end_date=leave_date,
employee=employee), 'name')
if prev_comp_leave_req:
return frappe.get_doc('Compensatory Leave Request', prev_comp_leave_req)
return frappe.get_doc(dict(
doctype='Compensatory Leave Request',
employee=employee,
leave_type='Compensatory Off',
work_from_date=leave_date,
work_end_date=leave_date,
reason='test'
)).insert()
def mark_attendance(employee, date=today(), status='Present'):
if not frappe.db.exists(dict(doctype='Attendance', employee=employee.name, attendance_date=date, status='Present')):
attendance = frappe.get_doc({
"doctype": "Attendance",
"employee": employee.name,
"attendance_date": date,
"status": status
})
attendance.save()
attendance.submit()
def create_holiday_list():
if frappe.db.exists("Holiday List", "_Test Compensatory Leave"):
return
holiday_list = frappe.get_doc({
"doctype": "Holiday List",
"from_date": add_months(today(), -3),
"to_date": add_months(today(), 3),
"holidays": [
{
"description": "Test Holiday",
"holiday_date": today()
},
{
"description": "Test Holiday 1",
"holiday_date": add_days(today(), -1)
}
],
"holiday_list_name": "_Test Compensatory Leave"
})
holiday_list.save()

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime
from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime, cstr
from frappe.model.naming import set_name_by_naming_series
from frappe import throw, _, scrub
from frappe.permissions import add_user_permission, remove_user_permission, \
@@ -218,8 +218,8 @@ class Employee(NestedSet):
def reset_employee_emails_cache(self):
prev_doc = self.get_doc_before_save() or {}
cell_number = self.get('cell_number')
prev_number = prev_doc.get('cell_number')
cell_number = cstr(self.get('cell_number'))
prev_number = cstr(prev_doc.get('cell_number'))
if (cell_number != prev_number or
self.get('user_id') != prev_doc.get('user_id')):
frappe.cache().hdel('employees_with_number', cell_number)

View File

@@ -6,6 +6,7 @@ from __future__ import unicode_literals
import frappe
import json
from frappe.model.document import Document
from frappe.utils import getdate
class EmployeeAttendanceTool(Document):
@@ -43,17 +44,26 @@ def get_employees(date, department = None, branch = None, company = None):
@frappe.whitelist()
def mark_employee_attendance(employee_list, status, date, leave_type=None, company=None):
employee_list = json.loads(employee_list)
for employee in employee_list:
attendance = frappe.new_doc("Attendance")
attendance.employee = employee['employee']
attendance.employee_name = employee['employee_name']
attendance.attendance_date = date
attendance.status = status
if status == "On Leave" and leave_type:
attendance.leave_type = leave_type
if company:
attendance.company = company
leave_type = leave_type
else:
attendance.company = frappe.db.get_value("Employee", employee['employee'], "Company")
leave_type = None
if not company:
company = frappe.db.get_value("Employee", employee['employee'], "Company")
attendance=frappe.get_doc(dict(
doctype='Attendance',
employee=employee.get('employee'),
employee_name=employee.get('employee_name'),
attendance_date=getdate(date),
status=status,
leave_type=leave_type,
company=company
))
attendance.insert()
attendance.submit()

View File

@@ -208,6 +208,24 @@ frappe.ui.form.on("Expense Claim", {
frm.refresh_fields();
},
grand_total: function(frm) {
frm.trigger("update_employee_advance_claimed_amount");
},
update_employee_advance_claimed_amount: function(frm) {
let amount_to_be_allocated = frm.doc.grand_total;
$.each(frm.doc.advances || [], function(i, advance){
if (amount_to_be_allocated >= advance.unclaimed_amount){
frm.doc.advances[i].allocated_amount = frm.doc.advances[i].unclaimed_amount;
amount_to_be_allocated -= advance.allocated_amount;
} else{
frm.doc.advances[i].allocated_amount = amount_to_be_allocated;
amount_to_be_allocated = 0;
}
frm.refresh_field("advances");
});
},
make_payment_entry: function(frm) {
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
@@ -300,7 +318,7 @@ frappe.ui.form.on("Expense Claim", {
row.advance_account = d.advance_account;
row.advance_paid = d.paid_amount;
row.unclaimed_amount = flt(d.paid_amount) - flt(d.claimed_amount);
row.allocated_amount = flt(d.paid_amount) - flt(d.claimed_amount);
row.allocated_amount = 0;
});
refresh_field("advances");
}

View File

@@ -43,9 +43,9 @@ class ExpenseClaim(AccountsController):
}[cstr(self.docstatus or 0)]
paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount)
precision = self.precision("total_sanctioned_amount")
precision = self.precision("grand_total")
if (self.is_paid or (flt(self.total_sanctioned_amount) > 0
and flt(self.total_sanctioned_amount, precision) == flt(paid_amount, precision))) \
and flt(self.grand_total, precision) == flt(paid_amount, precision))) \
and self.docstatus == 1 and self.approval_status == 'Approved':
self.status = "Paid"
elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved':

View File

@@ -69,10 +69,14 @@ class LeaveAllocation(Document):
def validate_allocation_overlap(self):
leave_allocation = frappe.db.sql("""
select name from `tabLeave Allocation`
where employee=%s and leave_type=%s and docstatus=1
and to_date >= %s and from_date <= %s""",
(self.employee, self.leave_type, self.from_date, self.to_date))
SELECT
name
FROM `tabLeave Allocation`
WHERE
employee=%s AND leave_type=%s
AND name <> %s AND docstatus=1
AND to_date >= %s AND from_date <= %s""",
(self.employee, self.leave_type, self.name, self.from_date, self.to_date))
if leave_allocation:
frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}")

View File

@@ -549,10 +549,10 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date):
leave_days += leave_entry.leaves
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \
and not skip_expiry_leaves(leave_entry, to_date):
and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date):
leave_days += leave_entry.leaves
else:
elif leave_entry.transaction_type == 'Leave Application':
if leave_entry.from_date < getdate(from_date):
leave_entry.from_date = from_date
if leave_entry.to_date > getdate(to_date):
@@ -579,14 +579,15 @@ def skip_expiry_leaves(leave_entry, date):
def get_leave_entries(employee, leave_type, from_date, to_date):
''' Returns leave entries between from_date and to_date '''
return frappe.db.sql("""
select employee, leave_type, from_date, to_date, leaves, transaction_type, is_carry_forward, transaction_name
from `tabLeave Ledger Entry`
where employee=%(employee)s and leave_type=%(leave_type)s
and docstatus=1
and leaves<0
and (from_date between %(from_date)s and %(to_date)s
or to_date between %(from_date)s and %(to_date)s
or (from_date < %(from_date)s and to_date > %(to_date)s))
SELECT
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type,
is_carry_forward, is_expired
FROM `tabLeave Ledger Entry`
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
AND docstatus=1 AND leaves<0
AND (from_date between %(from_date)s AND %(to_date)s
OR to_date between %(from_date)s AND %(to_date)s
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
""", {
"from_date": from_date,
"to_date": to_date,
@@ -773,4 +774,4 @@ def get_leave_approver(employee):
leave_approver = frappe.db.get_value('Department Approver', {'parent': department,
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
return leave_approver
return leave_approver

View File

@@ -43,10 +43,18 @@ class TestLeavePeriod(unittest.TestCase):
leave_period.grant_leave_allocation(employee=employee_doc_name)
self.assertEqual(get_leave_balance_on(employee_doc_name, leave_type, today()), 20)
def create_leave_period(from_date, to_date):
def create_leave_period(from_date, to_date, company=None):
leave_period = frappe.db.get_value('Leave Period',
dict(company=company or erpnext.get_default_company(),
from_date=from_date,
to_date=to_date,
is_active=1), 'name')
if leave_period:
return frappe.get_doc("Leave Period", leave_period)
leave_period = frappe.get_doc({
"doctype": "Leave Period",
"company": erpnext.get_default_company(),
"company": company or erpnext.get_default_company(),
"from_date": from_date,
"to_date": to_date,
"is_active": 1

View File

@@ -321,11 +321,11 @@ def allocate_earned_leaves():
if new_allocation == allocation.total_leaves_allocated:
continue
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
create_earned_leave_ledger_entry(allocation, earned_leaves, today)
create_additional_leave_ledger_entry(allocation, earned_leaves, today)
def create_earned_leave_ledger_entry(allocation, earned_leaves, date):
''' Create leave ledger entry based on the earned leave frequency '''
allocation.new_leaves_allocated = earned_leaves
def create_additional_leave_ledger_entry(allocation, leaves, date):
''' Create leave ledger entry for leave types '''
allocation.new_leaves_allocated = leaves
allocation.from_date = date
allocation.unused_leaves = 0
allocation.create_leave_ledger_entry()
@@ -389,6 +389,7 @@ def get_sal_slip_total_benefit_given(employee, payroll_period, component=False):
def get_holidays_for_employee(employee, start_date, end_date):
holiday_list = get_holiday_list_for_employee(employee)
holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday`
where
parent=%(holiday_list)s
@@ -437,4 +438,4 @@ def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, co
}, as_dict=True)
if sum_of_claimed_amount and flt(sum_of_claimed_amount[0].total_amount) > 0:
total_claimed_amount = sum_of_claimed_amount[0].total_amount
return total_claimed_amount
return total_claimed_amount

View File

@@ -89,7 +89,8 @@
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"fieldname": "section_break_12",
@@ -129,7 +130,7 @@
}
],
"is_submittable": 1,
"modified": "2019-10-16 13:38:32.302316",
"modified": "2019-11-18 19:37:37.151686",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Blanket Order",

View File

@@ -1,298 +1,78 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-05-24 07:20:04.255236",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"creation": "2018-05-24 07:20:04.255236",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
"column_break_3",
"qty",
"rate",
"ordered_qty",
"section_break_7",
"terms_and_conditions"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item_code",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code",
"length": 0,
"no_copy": 0,
"options": "Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Code",
"options": "Item",
"reqd": 1,
"search_index": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "item_code.item_name",
"fieldname": "item_name",
"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": "Item Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fetch_from": "item_code.item_name",
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Quantity",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Quantity"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rate",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Rate",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Rate",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ordered_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Ordered Quantity",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "ordered_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Ordered Quantity",
"no_copy": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_7",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "terms_and_conditions",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Terms and Conditions",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldname": "terms_and_conditions",
"fieldtype": "Text",
"label": "Terms and Conditions"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-06-14 07:04:14.050836",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Blanket Order Item",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
],
"istable": 1,
"modified": "2019-11-18 19:37:46.245878",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Blanket Order Item",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -5,6 +5,12 @@ frappe.provide("erpnext.bom");
frappe.ui.form.on("BOM", {
setup: function(frm) {
frm.custom_make_buttons = {
'BOM': 'Duplicate BOM',
'Work Order': 'Work Order',
'Quality Inspection': 'Quality Inspection'
};
frm.set_query("bom_no", "items", function() {
return {
filters: {
@@ -85,9 +91,21 @@ frappe.ui.form.on("BOM", {
}
if(frm.doc.docstatus!=0) {
frm.add_custom_button(__("Duplicate"), function() {
frm.add_custom_button(__("Duplicate BOM"), function() {
frm.copy_doc();
});
}, __("Create"));
frm.add_custom_button(__("Work Order"), function() {
frm.trigger("make_work_order");
}, __("Create"));
if (frm.doc.inspection_required) {
frm.add_custom_button(__("Quality Inspection"), function() {
frm.trigger("make_quality_inspection");
}, __("Create"));
}
frm.page.set_inner_btn_group_as_primary(__('Create'));
}
if(frm.doc.items && frm.doc.allow_alternative_item) {
@@ -109,6 +127,41 @@ frappe.ui.form.on("BOM", {
}
},
make_work_order: function(frm) {
const fields = [{
fieldtype: 'Float',
label: __('Qty To Manufacture'),
fieldname: 'qty',
reqd: 1,
default: 1
}];
frappe.prompt(fields, data => {
frappe.call({
method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
args: {
item: frm.doc.item,
qty: data.qty || 0.0,
project: frm.doc.project
},
freeze: true,
callback: function(r) {
if(r.message) {
var doc = frappe.model.sync(r.message)[0];
frappe.set_route("Form", doc.doctype, doc.name);
}
}
});
}, __("Enter Value"), __("Create"));
},
make_quality_inspection: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.quality_inspection.quality_inspection.make_quality_inspection",
frm: frm
})
},
update_cost: function(frm) {
return frappe.call({
doc: frm.doc,

View File

@@ -3,33 +3,36 @@
"creation": "2013-01-22 15:11:38",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"item",
"item_name",
"image",
"uom",
"quantity",
"set_rate_of_sub_assembly_item_based_on_bom",
"cb0",
"is_active",
"is_default",
"with_operations",
"inspection_required",
"allow_alternative_item",
"allow_same_item_multiple_times",
"set_rate_of_sub_assembly_item_based_on_bom",
"quality_inspection_template",
"image",
"item_name",
"uom",
"currency_detail",
"company",
"transfer_material_against",
"project",
"conversion_rate",
"column_break_12",
"currency",
"rm_cost_as_per",
"buying_price_list",
"operations_section",
"section_break_21",
"with_operations",
"column_break_23",
"transfer_material_against",
"routing",
"operations_section",
"operations",
"materials_section",
"inspection_required",
"quality_inspection_template",
"items",
"scrap_section",
"scrap_items",
@@ -41,14 +44,9 @@
"base_operating_cost",
"base_raw_material_cost",
"base_scrap_material_cost",
"total_cost_of_bom",
"total_cost",
"column_break_26",
"total_cost",
"base_total_cost",
"more_info_section",
"project",
"amended_from",
"col_break23",
"section_break_25",
"description",
"column_break_27",
@@ -57,12 +55,14 @@
"website_section",
"show_in_website",
"route",
"column_break_52",
"website_image",
"thumbnail",
"sb_web_spec",
"web_long_description",
"show_items",
"show_operations"
"show_operations",
"web_long_description",
"amended_from"
],
"fields": [
{
@@ -152,7 +152,7 @@
"default": "0",
"fieldname": "inspection_required",
"fieldtype": "Check",
"label": "Inspection Required"
"label": "Quality Inspection Required"
},
{
"default": "0",
@@ -160,12 +160,6 @@
"fieldtype": "Check",
"label": "Allow Alternative Item"
},
{
"default": "0",
"fieldname": "allow_same_item_multiple_times",
"fieldtype": "Check",
"label": "Allow Same Item Multiple Times"
},
{
"allow_on_submit": 1,
"default": "1",
@@ -193,6 +187,7 @@
"reqd": 1
},
{
"default": "Work Order",
"fieldname": "transfer_material_against",
"fieldtype": "Select",
"label": "Transfer Material Against",
@@ -235,10 +230,10 @@
{
"fieldname": "operations_section",
"fieldtype": "Section Break",
"label": "Operations",
"oldfieldtype": "Section Break"
},
{
"depends_on": "with_operations",
"fieldname": "routing",
"fieldtype": "Link",
"label": "Routing",
@@ -335,10 +330,6 @@
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "total_cost_of_bom",
"fieldtype": "Section Break"
},
{
"fieldname": "total_cost",
"fieldtype": "Currency",
@@ -359,10 +350,6 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "more_info_section",
"fieldtype": "Section Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
@@ -381,10 +368,6 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "col_break23",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_25",
"fieldtype": "Section Break"
@@ -481,13 +464,26 @@
"fieldname": "show_operations",
"fieldtype": "Check",
"label": "Show Operations"
},
{
"fieldname": "section_break_21",
"fieldtype": "Section Break",
"label": "Operations"
},
{
"fieldname": "column_break_23",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_52",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-sitemap",
"idx": 1,
"image_field": "image",
"is_submittable": 1,
"modified": "2019-07-30 17:00:09.665068",
"modified": "2019-11-22 14:35:12.142150",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",

View File

@@ -65,6 +65,7 @@ class BOM(WebsiteGenerator):
context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
def on_update(self):
frappe.cache().hdel('bom_children', self.name)
self.check_recursion()
self.update_stock_qty()
self.update_exploded_items()
@@ -96,6 +97,7 @@ class BOM(WebsiteGenerator):
def get_routing(self):
if self.routing:
self.set("operations", [])
for d in frappe.get_all("BOM Operation", fields = ["*"],
filters = {'parenttype': 'Routing', 'parent': self.routing}):
child = self.append('operations', d)
@@ -289,7 +291,7 @@ class BOM(WebsiteGenerator):
if not valuation_rate:
valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
return valuation_rate
return flt(valuation_rate)
def manage_default_bom(self):
""" Uncheck others if current one is selected as default or
@@ -362,15 +364,9 @@ class BOM(WebsiteGenerator):
def validate_materials(self):
""" Validate raw material entries """
def get_duplicates(lst):
seen = set()
seen_add = seen.add
for item in lst:
if item.item_code in seen or seen_add(item.item_code):
yield item
if not self.get('items'):
frappe.throw(_("Raw Materials cannot be blank."))
check_list = []
for m in self.get('items'):
if m.bom_no:
@@ -379,16 +375,6 @@ class BOM(WebsiteGenerator):
frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx))
check_list.append(m)
if not self.allow_same_item_multiple_times:
duplicate_items = list(get_duplicates(check_list))
if duplicate_items:
li = []
for i in duplicate_items:
li.append("{0} on row {1}".format(i.item_code, i.idx))
duplicate_list = '<br>' + '<br>'.join(li)
frappe.throw(_("Same item has been entered multiple times. {0}").format(duplicate_list))
def check_recursion(self, bom_list=[]):
""" Check whether recursion occurs in any bom"""
bom_list = self.traverse_tree()

View File

@@ -17,11 +17,13 @@ def get_data():
},
{
'label': _('Manufacture'),
'items': ['BOM', 'Work Order', 'Job Card', 'Production Plan']
'items': ['BOM', 'Work Order', 'Job Card']
},
{
'label': _('Purchase'),
'label': _('Subcontract'),
'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
}
]
],
'disable_create_buttons': ["Item", "Purchase Order", "Purchase Receipt",
"Purchase Invoice", "Job Card", "Stock Entry"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@ from frappe import _
from six import string_types
from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order
from frappe.model.document import Document
import click
class BOMUpdateTool(Document):
def replace_bom(self):
@@ -17,7 +18,8 @@ class BOMUpdateTool(Document):
frappe.cache().delete_key('bom_children')
bom_list = self.get_parent_boms(self.new_bom)
updated_bom = []
with click.progressbar(bom_list) as bom_list:
pass
for bom in bom_list:
try:
bom_obj = frappe.get_cached_doc('BOM', bom)

View File

@@ -529,7 +529,6 @@ def get_material_request_items(row, sales_order,
required_qty = ceil(required_qty)
if required_qty > 0:
print(row)
return {
'item_code': row.item_code,
'item_name': row.item_name,

View File

@@ -581,6 +581,8 @@ erpnext.work_order = {
description: __('Max: {0}', [max]),
default: max
}, data => {
max += (max * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
if (data.qty > max) {
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
reject();

View File

@@ -37,7 +37,7 @@ class WorkOrder(Document):
ms = frappe.get_doc("Manufacturing Settings")
self.set_onload("material_consumption", ms.material_consumption)
self.set_onload("backflush_raw_materials_based_on", ms.backflush_raw_materials_based_on)
self.set_onload("overproduction_percentage", ms.overproduction_percentage_for_work_order)
def validate(self):
self.validate_production_item()
@@ -609,6 +609,23 @@ def get_item_details(item, project = None):
return res
@frappe.whitelist()
def make_work_order(item, qty=0, project=None):
if not frappe.has_permission("Work Order", "write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
item_details = get_item_details(item, project)
wo_doc = frappe.new_doc("Work Order")
wo_doc.production_item = item
wo_doc.update(item_details)
if flt(qty) > 0:
wo_doc.qty = flt(qty)
wo_doc.get_items_and_operations_from_bom()
return wo_doc
@frappe.whitelist()
def check_if_scrap_warehouse_mandatory(bom_no):
res = {"set_scrap_wh_mandatory": False }

View File

@@ -645,4 +645,6 @@ erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
erpnext.patches.v12_0.set_payment_entry_status
erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields
erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
erpnext.patches.v12_0.update_price_or_product_discount
erpnext.patches.v12_0.add_export_type_field_in_party_master

View File

@@ -15,13 +15,6 @@ def execute():
rename_field(doctype, "allow_transfer_for_manufacture", "include_item_in_manufacturing")
if frappe.db.has_column('BOM', 'allow_same_item_multiple_times'):
frappe.db.sql(""" UPDATE tabBOM
SET
allow_same_item_multiple_times = 0
WHERE
trim(coalesce(allow_same_item_multiple_times, '')) = '' """)
for doctype in ['BOM', 'Work Order']:
frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype))

View File

@@ -0,0 +1,40 @@
from __future__ import unicode_literals
import frappe
from erpnext.regional.india.setup import make_custom_fields
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
make_custom_fields()
frappe.reload_doctype('Tax Category')
frappe.reload_doctype('Sales Taxes and Charges Template')
frappe.reload_doctype('Purchase Taxes and Charges Template')
# Create tax category with inter state field checked
tax_category = frappe.db.get_value('Tax Category', {'name': 'OUT OF STATE'}, 'name')
if not tax_category:
inter_state_category = frappe.get_doc({
'doctype': 'Tax Category',
'title': 'OUT OF STATE',
'is_inter_state': 1
}).insert()
tax_category = inter_state_category.name
for doctype in ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template'):
template = frappe.db.get_value(doctype, {'is_inter_state': 1, 'disabled': 0}, ['name'])
if template:
frappe.db.set_value(doctype, template, 'tax_category', tax_category)
frappe.db.sql("""
DELETE FROM `tabCustom Field`
WHERE fieldname = 'is_inter_state'
AND dt IN ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template')
""")

View File

@@ -62,12 +62,12 @@ def execute():
]
for dt in doctypes:
for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item`
for d in frappe.db.sql("""select name, parenttype, parent, item_code, item_tax_rate from `tab{0} Item`
where ifnull(item_tax_rate, '') not in ('', '{{}}')
and item_tax_template is NULL""".format(dt), as_dict=1):
item_tax_map = json.loads(d.item_tax_rate)
item_tax_template_name = get_item_tax_template(item_tax_templates,
item_tax_map, d.item_code, d.parent)
item_tax_map, d.item_code, d.parenttype, d.parent)
frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name)
frappe.db.auto_commit_on_many_writes = False
@@ -77,7 +77,7 @@ def execute():
settings.determine_address_tax_category_from = "Billing Address"
settings.save()
def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None):
def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None):
# search for previously created item tax template by comparing tax maps
for template, item_tax_template_map in iteritems(item_tax_templates):
if item_tax_map == item_tax_template_map:
@@ -88,23 +88,44 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=No
item_tax_template.title = make_autoname("Item Tax Template-.####")
for tax_type, tax_rate in iteritems(item_tax_map):
if not frappe.db.exists("Account", tax_type):
account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type'], as_dict=1)
if account_details:
if account_details.account_type not in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'):
frappe.db.set_value('Account', account_details.name, 'account_type', 'Chargeable')
else:
parts = tax_type.strip().split(" - ")
account_name = " - ".join(parts[:-1])
company = frappe.db.get_value("Company", filters={"abbr": parts[-1]})
company = get_company(parts[-1], parenttype, parent)
parent_account = frappe.db.get_value("Account",
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
frappe.get_doc({
"doctype": "Account",
filters = {
"account_name": account_name,
"company": company,
"account_type": "Tax",
"parent_account": parent_account
}).insert()
"company": company,
"account_type": "Tax",
"parent_account": parent_account
}
tax_type = frappe.db.get_value("Account", filters)
if not tax_type:
account = frappe.new_doc("Account")
account.update(filters)
account.insert()
tax_type = account.name
item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
item_tax_templates.setdefault(item_tax_template.title, {})
item_tax_templates[item_tax_template.title][tax_type] = tax_rate
item_tax_template.save()
return item_tax_template.name
def get_company(company_abbr, parenttype=None, parent=None):
if parenttype and parent:
company = frappe.get_cached_value(parenttype, parent, 'company')
else:
company = frappe.db.get_value("Company", filters={"abbr": company_abbr})
if not company:
companies = frappe.get_all('Company')
if len(companies) == 1:
company = companies[0].name
return company

View File

@@ -7,6 +7,8 @@ def execute():
if not company:
return
frappe.reload_doc('accounts', 'doctype', 'Tax Category')
make_custom_fields()
for doctype in ['Sales Invoice', 'Purchase Invoice']:

View File

@@ -0,0 +1,8 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "pricing_rule")
frappe.db.sql(""" UPDATE `tabPricing Rule` SET price_or_product_discount = 'Price'
WHERE ifnull(price_or_product_discount,'') = '' """)

View File

@@ -302,6 +302,8 @@ def get_items(filters=None, search=None):
if isinstance(filters, dict):
filters = [['Item', fieldname, '=', value] for fieldname, value in filters.items()]
enabled_items_filter = get_conditions({ 'disabled': 0 }, 'and')
show_in_website_condition = ''
if products_settings.hide_variants:
show_in_website_condition = get_conditions({'show_in_website': 1 }, 'and')
@@ -313,19 +315,32 @@ def get_items(filters=None, search=None):
search_condition = ''
if search:
# Default fields to search from
default_fields = {'name', 'item_name', 'description', 'item_group'}
# Get meta search fields
meta = frappe.get_meta("Item")
meta_fields = set(meta.get_search_fields())
# Join the meta fields and default fields set
search_fields = default_fields.union(meta_fields)
try:
if frappe.db.count('Item', cache=True) > 50000:
search_fields.remove('description')
except KeyError:
pass
# Build or filters for query
search = '%{}%'.format(search)
or_filters = [
['name', 'like', search],
['item_name', 'like', search],
['description', 'like', search],
['item_group', 'like', search]
]
or_filters = [[field, 'like', search] for field in search_fields]
search_condition = get_conditions(or_filters, 'or')
filter_condition = get_conditions(filters, 'and')
where_conditions = ' and '.join(
[condition for condition in [show_in_website_condition, search_condition, filter_condition] if condition]
[condition for condition in [enabled_items_filter, show_in_website_condition, \
search_condition, filter_condition] if condition]
)
left_joins = []

View File

@@ -4,20 +4,16 @@ frappe.ui.form.on("Project", {
setup(frm) {
frm.make_methods = {
'Timesheet': () => {
let doctype = 'Timesheet';
frappe.model.with_doctype(doctype, () => {
let new_doc = frappe.model.get_new_doc(doctype);
// add a new row and set the project
let time_log = frappe.model.get_new_doc('Timesheet Detail');
time_log.project = frm.doc.name;
time_log.parent = new_doc.name;
time_log.parentfield = 'time_logs';
time_log.parenttype = 'Timesheet';
new_doc.time_logs = [time_log];
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
});
open_form(frm, "Timesheet", "Timesheet Detail", "time_logs");
},
'Purchase Order': () => {
open_form(frm, "Purchase Order", "Purchase Order Item", "items");
},
'Purchase Receipt': () => {
open_form(frm, "Purchase Receipt", "Purchase Receipt Item", "items");
},
'Purchase Invoice': () => {
open_form(frm, "Purchase Invoice", "Purchase Invoice Item", "items");
},
};
},
@@ -80,7 +76,7 @@ frappe.ui.form.on("Project", {
frm.events.set_status(frm, 'Cancelled');
}, __('Set Status'));
}
if (frappe.model.can_read("Task")) {
frm.add_custom_button(__("Gantt Chart"), function () {
frappe.route_options = {
@@ -123,3 +119,20 @@ frappe.ui.form.on("Project", {
},
});
function open_form(frm, doctype, child_doctype, parentfield) {
frappe.model.with_doctype(doctype, () => {
let new_doc = frappe.model.get_new_doc(doctype);
// add a new row and set the project
let new_child_doc = frappe.model.get_new_doc(child_doctype);
new_child_doc.project = frm.doc.name;
new_child_doc.parent = new_doc.name;
new_child_doc.parentfield = parentfield;
new_child_doc.parenttype = doctype;
new_doc[parentfield] = [new_child_doc];
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
});
}

View File

@@ -47,11 +47,11 @@ class Task(NestedSet):
if not self.project or frappe.flags.in_test:
return
expected_end_date = getdate(frappe.db.get_value("Project", self.project, "expected_end_date"))
expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date")
if expected_end_date:
validate_project_dates(expected_end_date, self, "exp_start_date", "exp_end_date", "Expected")
validate_project_dates(expected_end_date, self, "act_start_date", "act_end_date", "Actual")
validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected")
validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual")
def validate_status(self):
if self.status!=self.get_db_value("status") and self.status == "Completed":
@@ -278,4 +278,4 @@ def validate_project_dates(project_end_date, task, task_start, task_end, actual_
frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date))
if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0:
frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date))
frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date))

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