Compare commits

...

323 Commits

Author SHA1 Message Date
Sahil Khan
47420578ae Merge branch 'version-11-hotfix' into version-11 2019-09-26 13:55:31 +05:30
Sahil Khan
35d8cd9f10 bumped to version 11.1.65 2019-09-26 14:15:31 +05:50
Rucha Mahabal
1e55ac6a4f fix(Report): Sales Register (#19170) 2019-09-25 16:03:09 +05:30
Nabin Hait
ff7a5d18d1 fix: Handling payments against credit/debit notes and party currency (#19152) 2019-09-24 19:52:06 +05:30
Rucha Mahabal
5a15dcc060 fix(Stock): item variant description (#19135) 2019-09-24 19:16:43 +05:30
Sahil Khan
cec6c87e09 Merge branch 'version-11-hotfix' into version-11 2019-09-24 18:14:44 +05:30
Sahil Khan
b2870b9426 bumped to version 11.1.64 2019-09-24 18:34:44 +05:50
rohitwaghchaure
e27600066c Merge pull request #19145 from rohitwaghchaure/fixed_imponibileimporto_for_the_previous_row_total
fix: ImponibileImporto not getting calculated properly
2019-09-24 18:11:16 +05:30
Nabin Hait
5b8c624b34 Merge branch 'version-11-hotfix' into fixed_imponibileimporto_for_the_previous_row_total 2019-09-24 11:46:24 +05:30
Anurag Mishra
9e8531d68e fix: provided delete permission (#19142) 2019-09-24 09:55:01 +05:30
Mangesh-Khairnar
ebeb137351 chore: remove unlinked letter head references (#19139) 2019-09-24 09:52:13 +05:30
Rohit Waghchaure
8c7d28a41a fix: ImponibileImporto not getting calculated properly 2019-09-23 17:39:55 +05:30
rohitwaghchaure
d90b80af86 Merge pull request #19128 from rohitwaghchaure/removed_mandatory_property_for_address_field_in_quick_entry_v11_hotfix
fix: removed mandatory property for address field in quick entry
2019-09-23 14:48:41 +05:30
Anurag Mishra
b62e17f7f2 feat: missmatching amount in GST Sales report and itemised sales report (#19119) 2019-09-20 22:59:26 +05:30
Sahil Khan
5169dc1566 Merge branch 'version-11-hotfix' into version-11 2019-09-20 15:13:04 +05:30
Sahil Khan
d3afecd1d9 bumped to version 11.1.63 2019-09-20 15:33:04 +05:50
Rohit Waghchaure
ec56984e12 fix: removed mandatory property for address field in quick entry 2019-09-20 12:20:37 +05:30
Marica
1c26623f22 fix: Report 'Payment Period based On Invoice Date' (#19124) 2019-09-20 11:40:17 +05:30
Anurag Mishra
02cb668f22 fix: dashboard + button not working (#19095) 2019-09-19 18:12:57 +05:30
rohitwaghchaure
bd749dce18 Merge pull request #19113 from rohitwaghchaure/fixed_get_bin_details_and_serial_nos_arugument_passing_issue_v11
fix: get_bin_details_and_serial_nos() takes at least 3 arguments (4 g…
2019-09-19 18:01:16 +05:30
Rohit Waghchaure
737a504a35 fix: get_bin_details_and_serial_nos() takes at least 3 arguments (4 given) 2019-09-19 17:11:14 +05:30
rohitwaghchaure
c719f842bc fix: set stock adjustment account for the raw materials instead of COGS (#19089) 2019-09-19 11:16:19 +05:30
Rohan
0d19139acf fix: only set times if job card is filled (#19106) 2019-09-18 20:06:48 +05:30
rohitwaghchaure
80973bb8de fix: not able to export accounts receivable summary report in excel (#19099) 2019-09-18 20:03:30 +05:30
Suraj Shetty
a9a7e84873 fix: Permission issue in Total Stock Summary report (#19103)
Data in "Total Stock Summary" report were not getting filtered
based on applied user permissions because some link fields had
wrong options
2019-09-18 20:01:54 +05:30
Sahil Khan
78776c42fe Merge branch 'version-11-hotfix' into version-11 2019-09-17 17:00:32 +05:30
Sahil Khan
2d9908d22f bumped to version 11.1.62 2019-09-17 17:20:32 +05:50
rohitwaghchaure
3b0695e0ca Merge pull request #19086 from rohitwaghchaure/offline_pos_currency_conversion_issue_for_v11_hotfix
fix: plc conversion issue for offline pos
2019-09-17 13:33:17 +05:30
Rohit Waghchaure
a9695f04de fix: plc conversion issue for offline pos 2019-09-17 13:25:16 +05:30
Jamsheer PP
a2398775cd fix: Asset Maintenance - TypeError - Version 11 (#19069)
* fix: Asset Maintenance - TypeError

* Update asset_maintenance.py
2019-09-16 19:42:38 +05:30
rohitwaghchaure
f2544ec7f2 Merge pull request #19067 from rohitwaghchaure/fixed_decimal_point_issue_for_e_invoice
fix: Decimal point issue for e-invoice
2019-09-16 14:51:50 +05:30
Rohit Waghchaure
778d7b9cbd fix: Decimal point issue for e-invoice 2019-09-16 14:49:30 +05:30
rohitwaghchaure
2a3c62e7f6 Merge pull request #19060 from rohitwaghchaure/user_can_edit_rate_and_discount_in_offline_pos
fix: user can able to change rate and discount even if they don't have permission
2019-09-16 11:50:33 +05:30
Rohit Waghchaure
5c576e4365 fix: user can able to change rate and discount even if they don't have permission 2019-09-16 11:14:33 +05:30
rohitwaghchaure
c57753a234 fix: for pos, paid amount has not considered the tax amount due to which outstadning amount showing for the pos invoices (#19038) 2019-09-13 15:49:11 +05:30
Sun Howwrongbum
7cabcdd9a2 fix: precision rounding issue during pos return validation (#19034) 2019-09-13 11:08:02 +05:30
Saurabh
2b0c845cb8 fix: if id not found then compare item name to avoid duplicate item creation (#19002) 2019-09-12 19:23:24 +05:30
Suraj Shetty
71e51714f2 fix: Naming series check to avoid duplicate entry error (#19016)
* fix: Naming series check to avoid duplicate key error

* fix: Check for existence of naming series
2019-09-12 19:18:40 +05:30
Anurag Mishra
62767ced3e feat: added date filter based on billing date and based date (#19013) 2019-09-12 13:48:18 +05:30
Himanshu
cf71643685 fix(Issue): track issue split from (#18995)
* fix: track issue split from

* fix: comment on issue split

* Update issue.py
2019-09-11 19:20:47 +05:30
Nabin Hait
d225f5993b Show draft future payments as well 2019-09-11 18:40:31 +05:30
Mangesh-Khairnar
3dc8941be7 fix(purchase-invoice): Update paid amount on creation of debit note (#18833)
* fix(purchase-invoice): set paid amount for purchase return

* fix(purchase-invoice): remove payment schedule on creation of debit note

* Update accounts_controller.py
2019-09-11 10:50:13 +05:30
rohitwaghchaure
dbebec8fcc fix: Quotation Trends report not working for filter group by customer (#18987) 2019-09-10 19:18:45 +05:30
Sahil Khan
4111965d2e Merge branch 'version-11-hotfix' into version-11 2019-09-10 14:32:30 +05:30
Sahil Khan
3ac54a0ba6 bumped to version 11.1.61 2019-09-10 14:52:29 +05:50
Nabin Hait
143f007272 fix: set raw material's batch based on main item's batch only if RM has batch no (#18978) 2019-09-10 14:07:37 +05:30
Suraj Shetty
3fc10a8da1 fix: Make address fields mandatory (#18980) 2019-09-10 14:07:01 +05:30
Mangesh-Khairnar
ed1e28b8fa fix(packing-slip): add a more descriptive message (#18982) 2019-09-10 13:21:19 +05:30
Deepesh Garg
71145c699f Merge pull request #18973 from Anurag810/sales_invoice_print_format_fixes_v11
fix: sales invoice return print format overriding the meta default print format
2019-09-09 18:35:49 +05:30
Anurag Mishra
ea6d90988f fix: sales invoice return print format overideing the meta default print format 2019-09-09 16:05:23 +05:30
Faris Ansari
6efe4acb1d fix: Set Price List in case of User Permissions (#18968)
- Pick Price List if there is one User Permission record
2019-09-09 14:28:12 +05:30
Rohan
ad2365502f fix: only select serial no that are present in the selected batch (#18726) 2019-09-09 13:49:02 +05:30
rohitwaghchaure
9c428ebfba fix: not able to save item because price list has disabled (#18965) 2019-09-09 13:20:15 +05:30
rohitwaghchaure
b53e4404dc fix: '>' not supported between instances of 'int' and 'str' (#18966) 2019-09-09 13:19:22 +05:30
rohitwaghchaure
0bc849975c Merge pull request #18951 from rohitwaghchaure/fixed_decimal_point_issue_for_e_invoicing
fix: PrezzoUnitario decimal issue
2019-09-06 18:47:59 +05:30
rohitwaghchaure
690139ca11 fix: circular dependency during asset cancellation (#18952) 2019-09-06 18:39:25 +05:30
Rohit Waghchaure
c0a21ad96a fix: PrezzoUnitario decimal issue 2019-09-06 16:30:05 +05:30
rohitwaghchaure
8c9fd62775 fix: ImponibileImporto for On Previous Row Total (#18947) 2019-09-06 12:14:45 +05:30
Sahil Khan
0be054ae0f Merge branch 'version-11-hotfix' into version-11 2019-09-05 17:25:12 +05:30
Sahil Khan
912cf9d555 bumped to version 11.1.60 2019-09-05 17:45:11 +05:50
Nabin Hait
7fe0a2023a fix: Print/PDF of AR/AP report after refactoring (#18930) 2019-09-05 16:43:06 +05:30
Mohammad Noureldin
cf191c0483 18861: Updated .gitignore (#18862) 2019-09-05 15:15:59 +05:30
Ernesto Ruiz
bb8d9f2a57 fix: Add transtlation function to strings (#18806)
* fix: Add transtlation function to strings

fix: Add transtlation function to strings

* fix: Add transtlation function to strings

fix: Add transtlation function to strings
2019-09-05 15:04:42 +05:30
Bassam Ramadan
fa4577d18a Update journal_entry.js (#18922)
adding a missed semicolom
2019-09-05 14:55:58 +05:30
Rohan
993521d365 fix: pull project from task (#18775) 2019-09-05 14:53:48 +05:30
Rohan
3372b7411d fix: error while trying to get directions (#18826) 2019-09-05 14:51:04 +05:30
rohitwaghchaure
5dbfeb8557 fix: not able to create invoice against patient (#18857) 2019-09-05 14:48:34 +05:30
rohitwaghchaure
f5d2337176 fix: mismatch between warehouse tree value and warehouse based stock balance report value (#18877) 2019-09-05 14:47:10 +05:30
Faris Ansari
67aa7b8735 fix: Honor Shopping Cart Price List (#18884) 2019-09-05 12:19:35 +05:30
Anurag Mishra
aea67c9843 fix: added missing positional argument (#18897) 2019-09-05 12:18:01 +05:30
Rohan
f64aaf5e1c fix: attribute error when trying to fetch items (#18899) 2019-09-05 12:16:44 +05:30
Deepesh Garg
00f6fd827e fix: Add UOM in anlytics report when viewing based on item (#18901) 2019-09-05 12:15:25 +05:30
Sahil Khan
ea2cdf8c12 Merge branch 'version-11-hotfix' into version-11 2019-09-03 16:30:00 +05:30
Sahil Khan
29729c8d31 bumped to version 11.1.59 2019-09-03 16:50:00 +05:50
rohitwaghchaure
536e24c6a1 Merge pull request #18908 from rohitwaghchaure/update_qty_even_after_submission_of_the_sales_transaction
fix: update actual qty even after submission of the sales transactions
2019-09-03 15:59:26 +05:30
Rohit Waghchaure
f178dc3b26 fix: update actual qty even after submission of the sales transactions 2019-09-03 15:57:53 +05:30
Mangesh-Khairnar
bc2f311a41 fix(purchase-invoice): add rounded total property setter for purchase invoice (#18842) 2019-09-03 14:18:31 +05:30
Deepesh Garg
5c28aba872 fix: Default message from Payment Gateway Account not fetching (#18849) 2019-09-03 14:17:15 +05:30
Rohan
f800050b71 fix: include start and end date for contract status (#18865) 2019-09-03 14:16:33 +05:30
Nabin Hait
fea996303f fix: Test case for Accounts Receivable based on payment terms (#18896) 2019-09-03 13:54:40 +05:30
rohitwaghchaure
833e017111 Merge pull request #18886 from rohitwaghchaure/fix_incorrect_stock_valuation_calculate_when_qty_from_negative_to_stock
fix: incorrect stock value difference when stock move from negative to positive
2019-09-02 23:19:56 +05:30
Nabin Hait
fb4f73605a refactor: Accounts Receivable / Payable report (#18890) 2019-09-02 22:36:20 +05:30
Mangesh-Khairnar
94dfcce434 fix: loan application (#18881)
* fix: loan application

* Update loan_application.py
2019-09-02 15:59:07 +05:30
Rohit Waghchaure
18e5bc1976 fix: incorrect stock value difference when stock move from negative to positive 2019-09-02 11:57:54 +05:30
Saurabh
7e54e6acb8 Merge branch 'version-11-hotfix' into version-11 2019-08-29 20:11:51 +05:30
Saurabh
0e52951db8 bumped to version 11.1.58 2019-08-29 20:41:51 +06:00
rohitwaghchaure
1b050e2dfd Merge pull request #18869 from rohitwaghchaure/not_able_to_save_sales_order
fix: not able to save sales order
2019-08-29 20:02:41 +05:30
Rohit Waghchaure
5674e19e71 fix: not able to save sales order 2019-08-29 20:00:45 +05:30
Saurabh
c49735ed15 Merge branch 'version-11-hotfix' into version-11 2019-08-29 15:08:47 +05:30
Saurabh
d1c467e49c bumped to version 11.1.57 2019-08-29 15:38:47 +06:00
rohitwaghchaure
e14df31c05 Merge pull request #18864 from rohitwaghchaure/v11_optimized_code_to_get_items_for_pos
fix: optimized the code to fix slow loading of items in the POS
2019-08-29 12:22:30 +05:30
rohitwaghchaure
9ff9f21af0 Merge pull request #18856 from rohitwaghchaure/incorrect_valuation_rate_calculated_for_serial_no
fix: incorrect valuation rate calculated because of string replacement issue
2019-08-29 12:13:24 +05:30
Saurabh
eba361b27d Merge branch 'version-11-hotfix' into v11_optimized_code_to_get_items_for_pos 2019-08-29 12:05:44 +05:30
Rohit Waghchaure
cc960806a9 Optimized the code to fix slow loading of items in the POS 2019-08-29 11:46:55 +05:30
rohitwaghchaure
75c47d64ff Merge pull request #18824 from rohitwaghchaure/fix_actual_qty_showing_different_in_the_report_and_form
fix: Actual qty in the sales order showing different on the sales ord…
2019-08-29 09:23:32 +05:30
Rohit Waghchaure
300307b22f fix: incorrect valuation rate calculated because of string replacement issue 2019-08-28 16:56:07 +05:30
Mangesh-Khairnar
43ebcb5876 fix: update show in website on disabling item (#18832) 2019-08-26 10:27:21 +05:30
bghayad
f254d8c5ee Adding query for item field for some stock reports (#18457) 2019-08-23 13:00:39 +05:30
Nabin Hait
8811a8c9b6 Translated payment type 2019-08-23 12:48:43 +05:30
Ernesto Ruiz
4f1b88ee0f fix: fix: Add transtlation function to strings (#18801)
fix: Add transtlation function to strings
2019-08-23 12:31:11 +05:30
Ernesto Ruiz
51a7aa9c76 Fix: Add translation function to string (#18802)
Fix: Add translation function to string
2019-08-23 12:28:50 +05:30
Sun Howwrongbum
33ce5ce048 fix: pos return validation (#18810) 2019-08-23 12:25:18 +05:30
Sagar Vora
b4a570242e fix: incorrect value being passed to get_taxes function (#18509) 2019-08-23 12:12:26 +05:30
Rohit Waghchaure
481977d368 fix: Actual qty in the sales order showing different on the sales order form and the standard sales order report 2019-08-23 11:49:26 +05:30
Deepesh Garg
479b2bbbd9 fix: Duplicate items check in sales Invoice (#18662)
* fix: Duplicate items check in sales Invoice

* fix: Commonified function for duplicate items check in selling controller

* fix: Set valuation rate and transaction date in child tabel

* fix: Code cleanup
2019-08-23 11:42:49 +05:30
rohitwaghchaure
4faf57869d fix: POS Sync Issue (#18808) 2019-08-23 11:13:54 +05:30
Mangesh-Khairnar
33eb38cc1b fix: fetch capital work in progress as expense account (#18781) 2019-08-21 14:49:14 +05:30
Nabin Hait
c6f72f7c90 fix: Single gl entry should only be considered once either in opening or closing entry (#18793) 2019-08-21 14:47:25 +05:30
rohitwaghchaure
d082a12411 Merge pull request #18798 from scmmishra/shopping-cart
fix: shopping cart item availability
2019-08-21 08:59:37 +05:30
Shivam Mishra
096de8a0d5 fix: shopping cart item availability 2019-08-20 19:18:45 +05:30
Sahil Khan
5638d63866 Merge branch 'version-11-hotfix' into version-11 2019-08-20 15:58:20 +05:30
Sahil Khan
a621466219 bumped to version 11.1.56 2019-08-20 16:18:20 +05:50
rohitwaghchaure
6155114bfd fix: group by voucher consolidated showing incorrect data for deferred entries (#18778) 2019-08-20 12:35:38 +05:30
Mangesh-Khairnar
2ca33d4f51 Merge pull request #18782 from deepeshgarg007/test_case_fix_v11
fix: Failing sales and purchase return test cases
2019-08-19 19:21:05 +05:30
deepeshgarg007
f1618af76c fix: Syntax error 2019-08-19 18:53:11 +05:30
deepeshgarg007
9f7098115b fix: Failing sales and purchase return test cases 2019-08-19 17:53:47 +05:30
Deepesh Garg
55b8b4e374 fix: Travis (#18773) 2019-08-19 12:56:49 +05:30
Deepesh Garg
7cb8c51b2d fix: Party dashboard heatmap not capturing sales, purchase and other activities (#18752) 2019-08-19 11:49:47 +05:30
Anurag Mishra
311fe5b3dc fix: removed filters(not required) (#18728) 2019-08-19 10:30:44 +05:30
Anurag Mishra
269868130f fix: validated cost center in financial_statement (#18732)
* fix: validated cost center in financial_statement

* Update financial_statements.py
2019-08-19 10:27:53 +05:30
Mangesh-Khairnar
736eed01c2 fix: notify update on status change (#18764) 2019-08-19 10:20:48 +05:30
Mangesh-Khairnar
e014b898bd fix: valuation rate in stock ledger (#18743)
* fix: valuation rate in stock ledger

* test: allow zero valuation rate for items
2019-08-19 10:04:34 +05:30
Sahil Khan
1eb31210e0 Merge branch 'version-11-hotfix' into version-11 2019-08-16 17:32:19 +05:30
Sahil Khan
8d72aa99aa bumped to version 11.1.55 2019-08-16 17:52:19 +05:50
Deepesh Garg
3a4503b7a1 Merge pull request #18760 from deepeshgarg007/chart_of_accounts_fix
fix: Symbol fix in chart of accounts
2019-08-16 15:48:34 +05:30
Mangesh-Khairnar
01952bbc15 Merge pull request #18714 from Mangesh-Khairnar/quality-inspection-fix
fix(quality-inspection): fetch all items for inspection type in process
2019-08-16 13:54:31 +05:30
Mangesh-Khairnar
1ca6365dee Merge branch 'version-11-hotfix' into quality-inspection-fix 2019-08-16 13:54:11 +05:30
deepeshgarg007
a7a810a25f fix: Remove extra space 2019-08-16 12:58:24 +05:30
Suraj Shetty
e62fba062e fix: Check if account passed is accessible under Payment Entry (#18706)
* fix: Check if account passed is accessible under Payment Entry

* fix: Comment description

* fix: Ignore account permission check

* fix: Re-organise code
2019-08-14 14:47:24 +05:30
Mangesh-Khairnar
60b7acc9b0 fix(payroll-entry): commit submitted salary slip check onchange (#18695)
* fix(payroll-entry): commit submitted salary slip check onchange

* fix: track submitted ss via flags
2019-08-14 14:39:33 +05:30
Deepesh Garg
d7ddb0c14e Merge pull request #18718 from Anurag810/company_fix_v11
fix: removed hard coded string
2019-08-14 12:12:03 +05:30
Anurag Mishra
8d84ffb276 fix: removed hard coded string 2019-08-13 19:47:20 +05:30
Mangesh-Khairnar
784c3333d7 fix(quality-inspection): fetch all items for inspection type in process 2019-08-13 17:04:29 +05:30
Sahil Khan
2227f54e66 Merge branch 'version-11-hotfix' into version-11 2019-08-13 14:41:58 +05:30
Sahil Khan
c55657dca5 bumped to version 11.1.54 2019-08-13 15:01:58 +05:50
Anurag Mishra
b6b2c48340 fix: condition (#18708) 2019-08-13 12:53:17 +05:30
Suraj Shetty
582c86a7b6 fix: Filters for portal quotation list (#18704)
* fix: Filters for portal quotation list

* fix: Remove unwanted import
2019-08-12 17:33:04 +05:30
rohitwaghchaure
76668e9004 Merge pull request #18701 from rohitwaghchaure/reconciled_entry_still_has_no_clearance_date
fix: reconciled entry has not clearance date set
2019-08-12 15:34:05 +05:30
Rohit Waghchaure
d0426a6127 fix: reconciled entry has not clearance date set 2019-08-12 14:56:57 +05:30
DeeMysterio
4252575e84 fix(delivery note): change the text invoice to sales invoice on make button (#18665) 2019-08-12 13:03:39 +05:30
Anurag Mishra
f6dcd8ac91 payment ammount valication for pos invoiceds (#18664) 2019-08-12 12:10:48 +05:30
Andrew McLeod
6a9fb51dc5 fix: Python3 urllib use in item_group.py (now uses six.moves) (#18605) 2019-08-12 12:07:07 +05:30
Anurag Mishra
70b93e2cd4 fix: condition (#18667) 2019-08-12 11:46:47 +05:30
rohitwaghchaure
f4493674f7 Merge pull request #18680 from Anurag810/get-item-from-product-bundle-v11
fix: get item from product bundle
2019-08-09 15:17:07 +05:30
Anurag Mishra
dd5a818d6a fix: get item from product bundle 2019-08-09 13:35:01 +05:30
rohitwaghchaure
e78dfb5df6 Merge pull request #18668 from rohitwaghchaure/removed_stock_item_condition_for_sub_contract
fix: allow to subcontract service raw materials
2019-08-09 12:42:01 +05:30
Rohit Waghchaure
429d0dcda0 fix: allow to subcotract service raw materials 2019-08-08 18:19:24 +05:30
rohitwaghchaure
eb174e1afa fix: incorrect value of ImponibileImporto in the xml invoice (#18655) 2019-08-08 17:42:11 +05:30
rohitwaghchaure
4a6b5367ca fix: not able to transfer raw materials for subcontracted items (#18649) 2019-08-08 17:41:33 +05:30
Mangesh-Khairnar
3675e616bc fix: accounting period (#18476)
* fix: accounting period

* test: accounting period

* fix: account period creation

* fix: remove status field from accounting period
2019-08-08 17:38:56 +05:30
Deepesh Garg
f9d3df9531 fix: Changes in print format due to attribute name changes in frappe (#18638) 2019-08-08 15:52:00 +05:30
Aditya Hase
081d312a11 fix(quickbooks): Do not build global search for QuickBooks Migrator (#18626) 2019-08-08 15:47:22 +05:30
Shivam Mishra
355e832b02 fix: icon for opportunity (#18651) 2019-08-08 13:18:46 +05:30
Deepesh Garg
6b65e2b168 fix: Show Cr or Dr symbol in chart of accounts based on balance (#18652)
* fix: Show Cr or Dr symbol in chart of accounts based on balance

* Update account_tree.js
2019-08-08 13:16:35 +05:30
Sahil Khan
57041084d1 Merge branch 'version-11-hotfix' into version-11 2019-08-07 16:17:49 +05:30
Sahil Khan
cf4c7b406a bumped to version 11.1.53 2019-08-07 16:37:49 +05:50
Anurag Mishra
edb3cdbb59 fix: query (#18465) 2019-08-05 10:27:48 +05:30
Mangesh-Khairnar
55025d4dc9 fix(sales-order): update items (#18537)
* fix(sales-order): update items

* fix: minor changes
2019-08-05 10:16:10 +05:30
Deepesh Garg
6f7a5e525d fix: Make conversion rate optional for non itemized items (#18538) 2019-08-05 10:14:37 +05:30
Deepesh Garg
342c77c851 fix: Customer price list not honored in shopping cart (#18555) 2019-08-05 10:11:07 +05:30
Suraj Shetty
94f4d81b70 fix: Quotation list in customer portal (#18580)
* fix: Quotation list in customer portal

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

* fix: Remove duplicate code
2019-08-05 10:08:01 +05:30
Mangesh-Khairnar
74f31c6677 fix(employee-advance): update employee advance on change in expense claim (#18589)
* fix(employee-advance): update employee advance on rejection/cancellation of expense claim

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

* Update employee_advance.py
2019-08-05 10:03:32 +05:30
Deepesh Garg
7589b9f822 Merge pull request #18575 from deepeshgarg007/taxes_and_totals_v11
fix: Error handling in taxes and totals
2019-08-03 18:27:52 +05:30
deepeshgarg007
b761169976 fix: Error handling in taxes and totals 2019-08-03 13:40:37 +05:30
Suraj Shetty
537c3a79ab fix: Show correct label instead of showing undefined (#18561) 2019-08-02 21:46:11 +05:30
Sahil Khan
0510988b3e Merge branch 'version-11-hotfix' into version-11 2019-08-02 14:24:50 +05:30
Sahil Khan
79c389a48f bumped to version 11.1.52 2019-08-02 14:44:50 +05:50
Suraj Shetty
239779dad0 fix: Check zero valuation rate only for valid doctypes (#18530)
fix: Check zero valuation rate only for valid doctypes
2019-08-02 11:23:01 +05:30
Suraj Shetty
37a7ab9d0e Merge branch 'version-11-hotfix' into stock-ledger-fix_v11 2019-08-02 08:42:58 +05:30
Deepesh Garg
17ea15eb91 fix: Enhancement in credit note (#18534)
* fix: Credit note enhancement

* Fix: Print format for Sales Invoice Return

* fix: Zero quantity validation fix for credit note
2019-07-31 15:59:34 +05:30
rohitwaghchaure
ddaf81051e Merge pull request #18532 from rohitwaghchaure/scrap_item_not_adding_v11_hotfix
fix: scrap item not adding while making finished good entry
2019-07-31 12:09:52 +05:30
Rohit Waghchaure
d06f3882e9 fix: scrap item not adding while making finished good entry 2019-07-31 00:00:47 +05:30
deepeshgarg007
523994c2cf fix: Check zero valuation rate only for valid doctypes 2019-07-30 19:06:06 +05:30
Sahil Khan
00219ab7f0 Merge branch 'version-11-hotfix' into version-11 2019-07-30 13:06:37 +05:30
Sahil Khan
0c4f1e5e67 bumped to version 11.1.51 2019-07-30 13:26:37 +05:50
rohitwaghchaure
29e3cfd60f Merge pull request #18456 from rohitwaghchaure/bank_reco_consider_incorrect_paid_amount_field
fix: multi currency bank reconciliation issue
2019-07-29 11:40:25 +05:30
Mangesh-Khairnar
95058eac21 fix(payroll-entry): show make bank entry button when manually submitting salary slip (#18499) 2019-07-27 10:04:18 +05:30
Sahil Khan
4b963be8c4 Merge branch 'version-11-hotfix' into version-11 2019-07-26 12:43:33 +05:30
Sahil Khan
a4434b1a50 bumped to version 11.1.50 2019-07-26 13:03:33 +05:50
Deepesh Garg
d3dc241878 Merge pull request #18492 from adityahase/faster-version-11
perf(travis): Faster Builds
2019-07-26 12:40:27 +05:30
Deepesh Garg
012e617c55 Merge pull request #18479 from deepeshgarg007/bank_reco
fix: Dynamic link issue fix in Bank reconciliation statement
2019-07-26 08:49:14 +05:30
Deepesh Garg
bee135c8c6 Merge pull request #18472 from deepeshgarg007/gstr-1-query
fix: GSTR-1 report fixes
2019-07-26 08:46:24 +05:30
Aditya Hase
8a19cd1096 perf(tests): Remove unnecessary tearDown code 2019-07-26 06:03:21 +05:30
Aditya Hase
5b14ca081c perf(travis): Use setUpClass instead of setUp 2019-07-26 06:03:21 +05:30
Aditya Hase
4fbf03c760 perf(tests): Use country with lesser fixtures 2019-07-26 06:00:51 +05:30
Aditya Hase
503d1d3291 perf(tests): Do not unnecessarily create companies 2019-07-26 05:59:31 +05:30
Aditya Hase
e2108d75dc perf(tests): Use setUpClass and tearDownClass instead of setUp and tearDown 2019-07-26 05:59:31 +05:30
Aditya Hase
9a009a1d1c fix(travis): Install wkhtmltopdf 2019-07-26 05:59:30 +05:30
Aditya Hase
61211486bc fix(travis): Remove redundant arguments 2019-07-26 05:59:30 +05:30
Aditya Hase
42fd7451bf fix(travis): Use --site parameter 2019-07-26 05:59:30 +05:30
Aditya Hase
70a1c1bdac fix(travis): Merge install and before_script 2019-07-26 05:59:30 +05:30
Aditya Hase
eb6d74217c fix(travis): Execute get-app before bench start 2019-07-26 05:59:30 +05:30
Aditya Hase
172a8f1cea perf(travis): Use travis optimizations 2019-07-26 05:59:30 +05:30
Aditya Hase
164057fa5b perf(travis): Do not build assets 2019-07-26 05:59:30 +05:30
Aditya Hase
3c21ac6bb6 perf(travis): Remove unnecessary processes from Procfile 2019-07-26 05:59:30 +05:30
Aditya Hase
ad2585e1b7 fix(travis): Use MariaDB credentials from site_config.json 2019-07-26 05:59:30 +05:30
Aditya Hase
09fe604452 perf(travis): Do not waste 10 seconds 2019-07-26 05:59:30 +05:30
Aditya Hase
28d836cfea fix(travis): Execute get-app and reinstall after bench start 2019-07-26 05:59:30 +05:30
Aditya Hase
8005f350ae fix(travis): Do not disable scheduler using CLI 2019-07-26 05:59:30 +05:30
Aditya Hase
3168ae7dcc fix(travis): Do not use use_site 2019-07-26 05:59:30 +05:30
Aditya Hase
447b8dab96 fix(travis): Setup MariaDB 2019-07-26 05:59:30 +05:30
Aditya Hase
6408949d43 fix(travis): Remove unused files 2019-07-26 05:59:30 +05:30
Aditya Hase
5fe2272861 fix(travis): Remove travis/bench_init.sh 2019-07-26 05:58:21 +05:30
Aditya Hase
7165a5d1f8 fix(travis): Do not execute install.py 2019-07-26 05:58:21 +05:30
Aditya Hase
2ec21fa9fb fix(travis): Install coverall after tests 2019-07-26 05:58:21 +05:30
Aditya Hase
a162f560dd fix(travis): Use MariaDB 10.3 2019-07-26 05:58:21 +05:30
Aditya Hase
c3c168a70d fix(travis): Restructure build matrix 2019-07-26 05:58:21 +05:30
Aditya Hase
b5ac3b8a53 fix(travis): Remove unnecessary commands 2019-07-26 05:58:21 +05:30
deepeshgarg007
30452a8bcd Dynamic link issue fix in Bank reconciliation statement 2019-07-25 12:23:40 +05:30
deepeshgarg007
36676effd9 fix: Query for b2b 2019-07-24 21:19:08 +05:30
deepeshgarg007
f31ff74a75 fix: GSTR-1 report fixes 2019-07-24 20:52:25 +05:30
rohitwaghchaure
4298cc1cdb Merge pull request #18462 from rohitwaghchaure/pos_not_working_for_loyalty
fix: pos not working
2019-07-24 12:22:30 +05:30
Rohit Waghchaure
abe5a5ae30 fix: pos not working 2019-07-24 12:06:12 +05:30
Rohit Waghchaure
9daaeaed07 fix: multi currency bank reco issue 2019-07-23 23:13:33 +05:30
Sahil Khan
926150bccb Merge branch 'hotfix' 2019-07-23 14:38:23 +05:30
Sahil Khan
e46d10676e bumped to version 11.1.49 2019-07-23 14:58:23 +05:50
Mangesh-Khairnar
631e8bb9dd fix: remove wrong status update for order type maintenance (#18443) 2019-07-22 16:25:58 +05:30
rohitwaghchaure
13d6f4b291 fix: Pro rata calculation is not working for WDV depreciation method (#17746)
* fix: Pro rata calculation is not working for WDV depreciation method

* fixed test cases and the logic for pro rata calculation
2019-07-22 11:55:28 +05:30
Anurag Mishra
bc72fdff7a Handling case for Material Request type 'Manufacture' v11 (#18366)
* fix: status for 'material request type == Manufacture'

* Patch: for setting status

* fix: list view status
2019-07-22 11:34:55 +05:30
rohitwaghchaure
e8148112fc Merge pull request #18186 from bhavishyasharma/fix-bom-item-with-operation
fix: BOM Item with Operation
2019-07-22 11:28:54 +05:30
Mangesh-Khairnar
a1dd861096 fix: update percentage on creation of invoice for zero amt transaction (#18383) 2019-07-22 11:28:33 +05:30
Anurag Mishra
aeb0b008fa fix: mapped cost center in gl entries (#18405)
* fis: mapped cost center in gl entries

* chore: Removed Print statements
2019-07-22 11:21:44 +05:30
Himanshu
4c711ed62c fix(Item): File Attach via data import (#18413)
* fix: file not showing

* fix: file attach via import
2019-07-22 11:21:19 +05:30
Anurag Mishra
7620742299 fix: handle for product bundle (#18421) 2019-07-22 11:10:15 +05:30
rohitwaghchaure
8a5bd02662 Merge pull request #18441 from rohitwaghchaure/pos_not_working_for_hotfix
fix: pos not working
2019-07-21 22:57:16 +05:30
Rohit Waghchaure
565936442d fix: pos not working 2019-07-21 22:53:13 +05:30
Bhavishya Sharma
a8e1c90035 fix: updated logic 2019-07-20 19:49:47 +05:30
Deepesh Garg
a953b68a17 Merge pull request #18433 from deepeshgarg007/get_balance_hotfix
fix: Condition fix in get_balance_on function
2019-07-20 16:50:18 +05:30
deepeshgarg007
c9c48d3736 fix: Condition fix 2019-07-20 14:54:47 +05:30
deepeshgarg007
d1d679843e fix: Assignment fix 2019-07-20 14:54:40 +05:30
deepeshgarg007
c2b7084ee8 fix: Condition fix in get_balance_on function 2019-07-20 14:54:32 +05:30
Mangesh-Khairnar
07bbe8568f fix: payment document link fix (#18407) 2019-07-19 17:20:58 +05:30
Sahil Khan
2a58b6c20b Merge branch 'hotfix' 2019-07-19 17:11:27 +05:30
Sahil Khan
196bf2104f bumped to version 11.1.48 2019-07-19 17:31:27 +05:50
Deepesh Garg
a33145e6d9 Merge pull request #18400 from frappe/kennethsequeira-patch-1
fix: Update image in README file
2019-07-19 09:53:39 +05:30
Deepesh Garg
6161d3362d Merge pull request #18409 from rohitwaghchaure/not_able_to_make_material_request_for_product_bundle_from_so_hotfix
fix: not able to make material request for bundle items from the sale…
2019-07-19 09:47:43 +05:30
Deepesh Garg
fac8ed0630 Merge pull request #18389 from deepeshgarg007/lead_email
fix: Fetch email when making Opportunity from Lead
2019-07-19 08:05:46 +05:30
Rohit Waghchaure
3279ad55f6 fix: not able to make material request for bundle items from the sales order 2019-07-19 07:55:28 +05:30
Mangesh-Khairnar
9bdb04d7ca Merge pull request #18380 from rohitwaghchaure/pos_fixed_address_and_taxes_and_charges_not_set_as_per_profile_hotfix
fix: address and taxes not set as per pos profile in the pos invoice
2019-07-18 19:34:44 +05:30
Kenneth Sequeira
89270ef2c0 Update image in README file
Updated the link of the ERPNext logo image in the read me file
2019-07-18 17:01:23 +05:30
rohitwaghchaure
c0ea006571 Merge pull request #18392 from rohitwaghchaure/fix_send_sms_not_working_for_quotation
fix: send sms not working for the quotation
2019-07-18 15:46:01 +05:30
Mangesh-Khairnar
de3b79cce8 fix: precision on comparing with the outstanding amount (#18374) 2019-07-18 13:51:05 +05:30
Rohit Waghchaure
f326d5c9a1 fix: send sms not working for the quotation 2019-07-18 13:50:37 +05:30
Anurag Mishra
e112ac2345 fix: handled value Error (#18362) 2019-07-18 12:43:05 +05:30
Suraj Shetty
ad1719bbe4 feat(customer): Add report to show item prices per Customer (#17929)
feat(customer): Add report to show item prices per Customer
2019-07-18 10:15:05 +05:30
deepeshgarg007
f2c464dda8 fix: Fetch email when making Opportunity from Lead 2019-07-18 09:05:57 +05:30
Suraj Shetty
2b64b38a6e Merge branch 'hotfix' into hotfix-customer-item-price-report 2019-07-18 08:49:36 +05:30
rohitwaghchaure
dcbafecf58 Merge pull request #18384 from rohitwaghchaure/offline_pos_syncing_issue_for_customer_hotfix
fix: offline pos syncing issue for customer
2019-07-17 21:38:15 +05:30
rohitwaghchaure
20229b915c Merge pull request #18386 from rohitwaghchaure/cost_center_not_able_to_access
fix: cost center not able to access
2019-07-17 21:37:03 +05:30
Rohit Waghchaure
7a1a0fb5fb fix: cost center not able to access 2019-07-17 21:20:05 +05:30
Rohit Waghchaure
fddef7bd5c fix: offline pos syncing issue for customer 2019-07-17 20:34:50 +05:30
Rohit Waghchaure
804854f051 fix: address and taxes not set as per pos profile in the pos invoice 2019-07-17 19:34:42 +05:30
rohitwaghchaure
bdb48f34ed Merge pull request #18370 from Anurag810/fix_precision
fix: precision for base grand total amount
2019-07-17 19:00:18 +05:30
Suraj Shetty
986b5115da Merge branch 'hotfix' into hotfix-customer-item-price-report 2019-07-17 17:29:27 +05:30
Anurag Mishra
b37f4a157c fix precision 2019-07-17 14:53:25 +05:30
rohitwaghchaure
f650b287ef fix: conversion issue (#18357) 2019-07-16 15:58:43 +05:30
Sahil Khan
1866cebd14 Merge branch 'hotfix' 2019-07-16 15:23:51 +05:30
Sahil Khan
bb5bf06e0c bumped to version 11.1.47 2019-07-16 15:43:50 +05:50
Deepesh Garg
ecf8e20462 fix: Fixes in get_balance_on function (#18323) 2019-07-16 09:40:47 +05:30
Deepesh Garg
5a0bc043aa Merge pull request #18321 from deepeshgarg007/quo_opp_address_fix_hotfix
fix: Address filtering fix in opportunity
2019-07-16 09:03:07 +05:30
Deepesh Garg
a2c9c9505c Merge pull request #18349 from rohitwaghchaure/added_payment_terms_in_accounts_payble_summary
feat: added payment terms filter in the accounts payable summary
2019-07-16 08:39:50 +05:30
Rohit Waghchaure
f6060fa5f2 feat: added payment terms filter in the accounts payable summary 2019-07-16 01:53:34 +05:30
rohitwaghchaure
b4cf29391b fix: taxes sequence if shipping rule is set in the sales invoice (#18330) 2019-07-15 18:58:06 +05:30
Anurag Mishra
a6a6f69e26 fix: Validate loan repayment amount in Loan Application (#18170)
* fix: validate loan repayment amount

* fix: test for Loan Application
2019-07-15 14:16:45 +05:30
Mangesh-Khairnar
db38c02aee fix(bin): update requested qty in bin (#18313) 2019-07-15 14:08:12 +05:30
deepeshgarg007
e107d430eb fix: Address filtering fix in opportunity 2019-07-15 13:58:03 +05:30
Mangesh-Khairnar
0c849ac0f3 fix: bank-reconcillation-statement, payment entry links (#18312) 2019-07-15 11:59:59 +05:30
rohitwaghchaure
6b86bb462b Merge pull request #17845 from fproldan/fix_by_voucher_order
fix: General Ledger Group by Voucher not ordering by posting_date
2019-07-14 17:24:35 +05:30
rohitwaghchaure
80cfc88abb Merge pull request #18111 from sunhoww/pos_loyalty
fix: POS loyalty issues
2019-07-14 17:11:05 +05:30
rohitwaghchaure
3c425584a7 Merge pull request #18296 from rohitwaghchaure/fixed_dr_cr_issue_account_tree_hotfix
fix: Dr / Cr label for balance of the ledgers in the account tree
2019-07-14 17:01:13 +05:30
Mangesh-Khairnar
af561becc6 Merge pull request #17956 from harounasow/17902
fix(payment entry): payment to shareholder #17902
2019-07-13 19:10:17 +05:30
Mangesh-Khairnar
0ef08618c9 Merge branch 'hotfix' into 17902 2019-07-13 12:01:27 +05:30
Deepesh Garg
373d90f802 Merge pull request #18292 from nabinhait/taxes-calc-only-if-items
fix: Calculate taxes and totals only if items added in transaction
2019-07-12 23:03:41 +05:30
Mangesh-Khairnar
ceaedd0f95 fix: handle shareholder option in payment entry 2019-07-12 16:28:03 +05:30
Rohit Waghchaure
fd7c4e9822 fix: dr / cr label for balance of the ledgers in the account tree 2019-07-12 15:49:51 +05:30
Deepesh Garg
26878d907e Merge pull request #18000 from govindsmenokee/patch-2
fix: lead reference changed to dynamic link #17987
2019-07-12 14:41:00 +05:30
Mangesh-Khairnar
ae3c21046b Merge branch 'hotfix' into 17902 2019-07-12 14:39:56 +05:30
Nabin Hait
f01baaa56f fix: Calculate taxes and totals only if items added in transaction 2019-07-12 14:27:19 +05:30
Aditya Hase
ac534c64bc fix: QuickBooks Migrator (#18273)
* fix: Remove unused imports

* style: Remove whitespace before :

* style: Insert whitespace after ,

* style: One import per line

* fix: Remove unused local variable

* style: Remove multiple whitespace after :

* fix: Company might not have any warehouses when on_update hook for QBM is being executed

This used to throw while renaming a Company that is either used with QuickBooks
Migrator or is set as a Default Company in Global Defaults.
2019-07-12 11:19:53 +05:30
Rohan
6e8e42ca7c Merge branch 'hotfix' into hotfix-customer-item-price-report 2019-07-12 00:07:21 +05:30
Nabin Hait
7d3d99a9b8 fix: UX fixes in loan (#18219)
* fix: UX fixes in loan

* Update loan.py
2019-07-11 17:17:49 +05:30
Sahil Khan
9a858a9824 Merge branch 'hotfix' 2019-07-11 15:15:56 +05:30
Sahil Khan
5792d6037c bumped to version 11.1.46 2019-07-11 15:35:56 +05:50
Nabin Hait
75310637da fix: Revert #frappe/erpnext/17896 (#18250) 2019-07-11 14:39:38 +05:30
Mangesh-Khairnar
6fa4697efb fix(sales-invoice): get items from quotation (#18236) 2019-07-11 10:05:03 +05:30
Don-Leopardo
63e83cc005 Merge branch 'hotfix' into fix_by_voucher_order 2019-07-10 10:47:52 -03:00
Suraj Shetty
68d7d6e223 fix: Return fieldtype so that the client-side can format chart values (#18221) 2019-07-10 17:16:48 +05:30
Nabin Hait
7ee97b6128 fix: Make material request against SO only for pending qty (#18217) 2019-07-10 17:05:57 +05:30
Mangesh-Khairnar
49fadcbaf2 fix(exchange-rate-revaluation): change create to view button on creation of journal entry (#18202) 2019-07-10 17:03:08 +05:30
rohitwaghchaure
e6ab86e185 fix: error report for item price (#18213) 2019-07-10 16:41:39 +05:30
Sahil Khan
35fe0a6cbd Merge branch 'hotfix' 2019-07-09 15:20:08 +05:30
Sahil Khan
d287fe4dae bumped to version 11.1.45 2019-07-09 15:40:08 +05:50
Mangesh-Khairnar
683d987432 fix(bom): escape name with wildcard character (#18163) 2019-07-08 10:40:24 +05:30
rohitwaghchaure
37d0c75f04 fix: not able to make credit note for the sales invoice in which item code is not set (#18185) 2019-07-08 10:26:12 +05:30
Deepesh Garg
6940a6cd47 Merge pull request #18196 from deepeshgarg007/b2cs_json_hotfix
fix: Cess amount calculation fix in GSTR-1 report (hotfix)
2019-07-08 10:11:06 +05:30
deepeshgarg007
34867ced92 fix: Cess amount calculation fix in GSTR-1(hotfix) 2019-07-07 21:49:17 +05:30
Bhavishya Sharma
5c482e907e BOM Scrap Item fix 2019-07-06 00:20:21 +05:30
Bhavishya Sharma
3ebc13f228 typo fix 2019-07-05 23:33:19 +05:30
Bhavishya Sharma
b5882aaa6c fix: BOM Item with Operation 2019-07-05 22:40:49 +05:30
rohitwaghchaure
7543916da6 Merge pull request #18153 from netchampfaris/fix-price-list-on-website
fix: Honor price list in Shopping Cart Settings
2019-07-05 18:14:06 +05:30
rohitwaghchaure
11d02c028f fix: on credit note / debit note deferred reversed instead of income (#18171) 2019-07-05 14:19:40 +05:30
rohitwaghchaure
a72ef5eceb Merge pull request #18159 from rohitwaghchaure/set_default_supplier_in_item_default_for_multi_company
fix: default supplier was not set from the patch in item defaults for multi company instance
2019-07-05 11:21:34 +05:30
Rohit Waghchaure
ba62173561 fix: default supplier was not set from the patch in item defaults for multi company instance 2019-07-04 15:00:04 +05:30
Faris Ansari
e59836e3da fix: Honor price list in Shopping Cart Settings 2019-07-03 19:22:49 +05:30
Sun Howwrongbum
505332e680 feat(pos): reset payments and loyalty points on redeem uncheck 2019-06-29 21:35:58 +05:30
Sun Howwrongbum
ada3330403 fix(pos): changing loyalty points not updating payment amounts 2019-06-29 21:18:53 +05:30
Sun Howwrongbum
e0143e081c fix(pos): loyalty details not rendering in cart area 2019-06-29 20:48:27 +05:30
Don-Leopardo
f03522ad9e Merge branch 'hotfix' into fix_by_voucher_order 2019-06-27 18:09:39 -03:00
Don-Leopardo
f80be2d1f4 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-25 08:39:58 -03:00
Don-Leopardo
1bd60ac343 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-24 11:30:19 -03:00
Himanshu
2fc2d9b8ad Merge branch 'hotfix' into patch-2 2019-06-20 00:34:47 +05:30
Govind S Menokee
839b653f6b fix: lead reference changed to dynamic link #17987 2019-06-19 21:04:35 +05:30
Rohan Bansal
61cfa275f8 fix(customer): Improve performance by reducing queries 2019-06-17 12:14:09 +05:30
harounasow
7319bcb577 Merge branch 'hotfix' into 17902 2019-06-17 00:00:30 +02:00
Harouna Sow
3750a06d4f Merge branch '17902' of https://github.com/harounasow/erpnext into 17902 2019-06-16 23:45:19 +02:00
Harouna Sow
c79c8dae19 fix(payment entry): payment to shareholder 2019-06-16 23:42:57 +02:00
frappe user
1e4e2c81e9 fix(payment entry): payment to shareholder 2019-06-16 23:17:48 +02:00
Rohan Bansal
334106a6d0 feat(customer): Add report to show item prices per Customer 2019-06-13 14:46:21 +05:30
Francisco Roldán
eba2455ad3 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-11 10:22:26 -03:00
Don-Leopardo
63267d3616 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-10 17:28:17 -03:00
Don-Leopardo
5ed80a5ba9 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-10 08:23:07 -03:00
Don-Leopardo
98e5692a40 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-05 08:34:01 -03:00
NahuelOperto
bc293cf251 fix order in general ledger 2019-06-04 08:47:09 -03:00
166 changed files with 5414 additions and 4686 deletions

3
.gitignore vendored
View File

@@ -13,4 +13,5 @@ erpnext/docs/current
__pycache__
*~
.vscode/
node_modules/
node_modules/
.idea/

View File

@@ -1,51 +1,80 @@
language: python
dist: trusty
python:
- "2.7"
- "3.6"
language: python
env:
- TEST_TYPE="Server Side Test"
- TEST_TYPE="Patch Test"
git:
depth: 1
services:
- mysql
cache:
- pip
addons:
hosts: test_site
mariadb: 10.3
jobs:
include:
- name: "Python 2.7 Server Side Test"
python: 2.7
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 3.6 Server Side Test"
python: 3.6
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 2.7 Patch Test"
python: 2.7
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
- name: "Python 3.6 Patch Test"
python: 3.6
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
install:
# fix mongodb travis error
- sudo rm /etc/apt/sources.list.d/mongodb*.list
- pip install flake8==3.3.0
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
- sudo rm /etc/apt/sources.list.d/docker.list
- sudo apt-get install hhvm && rm -rf /home/travis/.kiex/
- sudo apt-get purge -y mysql-common mysql-server mysql-client
- cd ~
- nvm install 10
- pip install python-coveralls
- wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py
- sudo python install.py --develop --user travis --without-bench-setup
- sudo pip install -e ~/bench
- rm $TRAVIS_BUILD_DIR/.git/shallow
- bash $TRAVIS_BUILD_DIR/travis/bench_init.sh
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
- git clone https://github.com/frappe/bench --depth 1
- pip install -e ./bench
before_script:
- mysql -u root -ptravis -e 'create database test_frappe'
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench
- mkdir ~/frappe-bench/sites/test_site
- cp -r $TRAVIS_BUILD_DIR/.travis/site_config.json ~/frappe-bench/sites/test_site/
- mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
- mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- mysql -u root -e "CREATE DATABASE test_frappe"
- mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
- mysql -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
- mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
- mysql -u root -e "FLUSH PRIVILEGES"
- wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
- tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
- sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
- sudo chmod o+x /usr/local/bin/wkhtmltopdf
- cd ~/frappe-bench
- bench get-app erpnext $TRAVIS_BUILD_DIR
- bench use test_site
- bench reinstall --mariadb-root-username root --mariadb-root-password travis --yes
- bench scheduler disable
- sed -i 's/9000/9001/g' sites/common_site_config.json
- bench start &
- sleep 10
script:
- bash $TRAVIS_BUILD_DIR/travis/run-tests.sh
- sed -i 's/watch:/# watch:/g' Procfile
- sed -i 's/schedule:/# schedule:/g' Procfile
- sed -i 's/socketio:/# socketio:/g' Procfile
- sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
- bench get-app erpnext $TRAVIS_BUILD_DIR
- bench start &
- bench --site test_site reinstall --yes
after_script:
- pip install python-coveralls
- coveralls -b apps/erpnext -d ../../sites/.coverage

View File

@@ -6,7 +6,8 @@
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"run_selenium_tests": 1,
"host_name": "http://localhost:8000",
"root_login": "root",
"root_password": "travis",
"host_name": "http://test_site:8000",
"install_apps": ["erpnext"]
}

View File

@@ -1,5 +1,5 @@
<div align="center">
<img src="https://github.com/frappe/design/blob/master/logos/erpnext-logo.svg" height="128">
<img src="https://github.com/frappe/design/blob/master/logos/logo-2019/erpnext-logo.png" height="128">
<h2>ERPNext</h2>
<p align="center">
<p>ERP made simple</p>

View File

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

View File

@@ -121,7 +121,11 @@ frappe.treeview_settings["Account"] = {
},
onrender: function(node) {
if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){
var dr_or_cr = node.data.balance < 0 ? "Cr" : "Dr";
// show Dr if positive since balance is calculated as debit - credit else show Cr
let balance = node.data.balance_in_account_currency || node.data.balance;
let dr_or_cr = balance > 0 ? "Dr": "Cr";
if (node.data && node.data.balance!==undefined) {
$('<span class="balance-area pull-right text-muted small">'
+ (node.data.balance_in_account_currency ?

View File

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

View File

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

View File

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

View File

@@ -48,7 +48,10 @@ class BankAccount(Document):
# Encode characters as numbers
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
to_check = int(''.join(encoded))
try:
to_check = int(''.join(encoded))
except ValueError:
frappe.throw(_('IBAN is not valid'))
if to_check % 97 != 1:
frappe.throw(_('IBAN is not valid'))

View File

@@ -45,12 +45,12 @@ class BankTransaction(StatusUpdater):
def clear_linked_payment_entries(self):
for payment_entry in self.payment_entries:
allocated_amount = get_total_allocated_amount(payment_entry)
paid_amount = get_paid_amount(payment_entry)
paid_amount = get_paid_amount(payment_entry, self.currency)
if paid_amount and allocated_amount:
if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount):
frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).".format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount))))
elif flt(allocated_amount[0]["allocated_amount"]) == flt(paid_amount):
else:
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
self.clear_simple_entry(payment_entry)
@@ -80,9 +80,17 @@ def get_total_allocated_amount(payment_entry):
AND
bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True)
def get_paid_amount(payment_entry):
def get_paid_amount(payment_entry, currency):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "paid_amount")
paid_amount_field = "paid_amount"
if payment_entry.payment_document == 'Payment Entry':
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
paid_amount_field = ("base_paid_amount"
if doc.paid_to_account_currency == currency else "paid_amount")
return frappe.db.get_value(payment_entry.payment_document,
payment_entry.payment_entry, paid_amount_field)
elif payment_entry.payment_document == "Journal Entry":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_credit")

View File

@@ -21,9 +21,29 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
refresh: function(frm) {
if(frm.doc.docstatus==1) {
frm.add_custom_button(__('Make Journal Entry'), function() {
return frm.events.make_jv(frm);
});
frappe.db.get_value("Journal Entry Account", {
'reference_type': 'Exchange Rate Revaluation',
'reference_name': frm.doc.name,
'docstatus': 1
}, "sum(debit) as sum", (r) =>{
let total_amt = 0;
frm.doc.accounts.forEach(d=> {
total_amt = total_amt + d['new_balance_in_base_currency'];
});
if(total_amt === r.sum) {
frm.add_custom_button(__("Journal Entry"), function(){
frappe.route_options = {
'reference_type': 'Exchange Rate Revaluation',
'reference_name': frm.doc.name
};
frappe.set_route("List", "Journal Entry");
}, __("View"));
} else {
frm.add_custom_button(__('Create Journal Entry'), function() {
return frm.events.make_jv(frm);
});
}
}, 'Journal Entry');
}
},

View File

@@ -167,7 +167,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
filters: {
'account': row.account
}
}
};
});
me.frm.set_query("reference_name", "accounts", function(doc, cdt, cdn) {

View File

@@ -38,7 +38,10 @@ def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=Non
@frappe.whitelist()
def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0):
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
loyalty_program_name = loyalty_program or lp_details.loyalty_program
if not loyalty_program_name: return
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program_name)
lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules],

View File

@@ -558,7 +558,7 @@ def get_outstanding_reference_documents(args):
# Get negative outstanding sales /purchase invoices
negative_outstanding_invoices = []
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
if args.get("party_type") in ("Supplier", "Customer") and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"),
args.get("party"), args.get("party_account"), party_account_currency, company_currency)
@@ -589,7 +589,7 @@ def get_outstanding_reference_documents(args):
# Get all SO / PO which are not fully billed or aginst which full advance not paid
orders_to_be_billed = []
if (args.get("party_type") != "Student"):
if (args.get("party_type") in ("Supplier", "Customer")):
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
args.get("party"), party_account_currency, company_currency)
@@ -601,7 +601,7 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre
voucher_type = 'Sales Order'
elif party_type == "Supplier":
voucher_type = 'Purchase Order'
elif party_type == "Employee":
else:
voucher_type = None
# Add cost center condition
@@ -613,13 +613,18 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre
orders = []
if voucher_type:
ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
else:
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
orders = frappe.db.sql("""
select
name as voucher_no,
{ref_field} as invoice_amount,
({ref_field} - advance_paid) as outstanding_amount,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
transaction_date as posting_date
from
`tab{voucher_type}`
@@ -627,17 +632,18 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre
{party_type} = %s
and docstatus = 1
and ifnull(status, "") != "Closed"
and {ref_field} > advance_paid
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
and abs(100 - per_billed) > 0.01
{condition}
order by
transaction_date, name
""".format(**{
"ref_field": ref_field,
"rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
"condition": condition
}), party, as_dict=True)
}), (party), as_dict=True)
order_list = []
for d in orders:
@@ -713,9 +719,23 @@ def get_party_details(company, party_type, party, date, cost_center=None):
@frappe.whitelist()
def get_account_details(account, date, cost_center=None):
frappe.has_permission('Payment Entry', throw=True)
# to check if the passed account is accessible if the reference doctype is Payment Entry
account_list = frappe.get_list('Account', {
'name': account
}, reference_doctype='Payment Entry', limit=1)
# There might be some user permissions which will allow account under certain doctypes
# except for Payment Entry, only in such case we should throw permission error
if not account_list:
frappe.throw(_('Account: {0} is not permitted under Payment Entry').format(account))
account_balance = get_balance_on(account, date, cost_center=cost_center,
ignore_account_permission=True)
return frappe._dict({
"account_currency": get_account_currency(account),
"account_balance": get_balance_on(account, date, cost_center=cost_center),
"account_balance": account_balance,
"account_type": frappe.db.get_value("Account", account, "account_type")
})

View File

@@ -1,7 +1,6 @@
cur_frm.add_fetch("payment_gateway", "payment_account", "payment_account")
cur_frm.add_fetch("payment_gateway", "payment_gateway", "payment_gateway")
cur_frm.add_fetch("payment_gateway", "message", "message")
cur_frm.add_fetch("payment_gateway", "payment_url_message", "payment_url_message")
cur_frm.add_fetch("payment_gateway_account", "payment_account", "payment_account")
cur_frm.add_fetch("payment_gateway_account", "payment_gateway", "payment_gateway")
cur_frm.add_fetch("payment_gateway_account", "message", "message")
frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){
if (frm.doc.reference_doctype) {

View File

@@ -336,6 +336,7 @@ class PurchaseInvoice(BuyingController):
if not self.is_return:
self.update_against_document_in_jv()
self.update_billing_status_for_zero_amount_refdoc("Purchase Receipt")
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr()
@@ -484,9 +485,13 @@ class PurchaseInvoice(BuyingController):
"credit": flt(item.rm_supp_cost)
}, warehouse_account[self.supplier_warehouse]["account_currency"]))
elif not item.is_fixed_asset or (item.is_fixed_asset and is_cwip_accounting_disabled()):
expense_account = (item.expense_account
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
gl_entries.append(
self.get_gl_dict({
"account": item.expense_account if not item.enable_deferred_expense else item.deferred_expense_account,
"account": expense_account,
"against": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"debit_in_account_currency": (flt(item.base_net_amount,
@@ -772,6 +777,7 @@ class PurchaseInvoice(BuyingController):
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
unlink_ref_doc_from_payment_entries(self)
self.update_billing_status_for_zero_amount_refdoc("Purchase Receipt")
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr()

View File

@@ -20,11 +20,13 @@ test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Templ
test_ignore = ["Serial No"]
class TestPurchaseInvoice(unittest.TestCase):
def setUp(self):
@classmethod
def setUpClass(self):
unlink_payment_on_cancel_of_invoice()
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
def tearDown(self):
@classmethod
def tearDownClass(self):
unlink_payment_on_cancel_of_invoice(0)
def test_gl_entries_without_perpetual_inventory(self):
@@ -91,6 +93,7 @@ class TestPurchaseInvoice(unittest.TestCase):
pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name)
self.assertRaises(frappe.LinkExistsError, pi_doc.cancel)
unlink_payment_on_cancel_of_invoice()
def test_purchase_invoice_for_blocked_supplier(self):
supplier = frappe.get_doc('Supplier', '_Test Supplier')

View File

@@ -41,6 +41,8 @@ def get_pos_data():
items_list = get_items_list(pos_profile, doc.company)
customers = get_customers_list(pos_profile)
doc.plc_conversion_rate = update_plc_conversion_rate(doc, pos_profile)
return {
'doc': doc,
'default_customer': pos_profile.get('customer'),
@@ -53,7 +55,7 @@ def get_pos_data():
'batch_no_data': get_batch_no_data(),
'barcode_data': get_barcode_data(items_list),
'tax_data': get_item_tax_data(),
'price_list_data': get_price_list_data(doc.selling_price_list),
'price_list_data': get_price_list_data(doc.selling_price_list, doc.plc_conversion_rate),
'customer_wise_price_list': get_customer_wise_price_list(),
'bin_data': get_bin_data(pos_profile),
'pricing_rules': get_pricing_rule_data(doc),
@@ -62,6 +64,15 @@ def get_pos_data():
'meta': get_meta()
}
def update_plc_conversion_rate(doc, pos_profile):
conversion_rate = 1.0
price_list_currency = frappe.get_cached_value("Price List", doc.selling_price_list, "currency")
if pos_profile.get("currency") != price_list_currency:
conversion_rate = get_exchange_rate(price_list_currency,
pos_profile.get("currency"), nowdate(), args="for_selling") or 1.0
return conversion_rate
def get_meta():
doctype_meta = {
@@ -317,14 +328,14 @@ def get_item_tax_data():
return itemwise_tax
def get_price_list_data(selling_price_list):
def get_price_list_data(selling_price_list, conversion_rate):
itemwise_price_list = {}
price_lists = frappe.db.sql("""Select ifnull(price_list_rate, 0) as price_list_rate,
item_code from `tabItem Price` ip where price_list = %(price_list)s""",
{'price_list': selling_price_list}, as_dict=1)
for item in price_lists:
itemwise_price_list[item.item_code] = item.price_list_rate
itemwise_price_list[item.item_code] = item.price_list_rate * conversion_rate
return itemwise_price_list
@@ -432,7 +443,6 @@ def get_customer_id(doc, customer=None):
return cust_id
def make_customer_and_address(customers):
customers_list = []
for customer, data in iteritems(customers):
@@ -449,8 +459,11 @@ def make_customer_and_address(customers):
frappe.db.commit()
return customers_list
def add_customer(data):
customer = data.get('full_name') or data.get('customer')
if frappe.db.exists("Customer", customer.strip()):
return customer.strip()
customer_doc = frappe.new_doc('Customer')
customer_doc.customer_name = data.get('full_name') or data.get('customer')
customer_doc.customer_pos_id = data.get('customer_pos_id')
@@ -462,21 +475,18 @@ def add_customer(data):
frappe.db.commit()
return customer_doc.name
def get_territory(data):
if data.get('territory'):
return data.get('territory')
return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories')
def get_customer_group(data):
if data.get('customer_group'):
return data.get('customer_group')
return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name')
def make_contact(args, customer):
if args.get('email_id') or args.get('phone'):
name = frappe.db.get_value('Dynamic Link',
@@ -502,7 +512,6 @@ def make_contact(args, customer):
doc.flags.ignore_mandatory = True
doc.save(ignore_permissions=True)
def make_address(args, customer):
if not args.get('address_line1'):
return
@@ -517,7 +526,10 @@ def make_address(args, customer):
address = frappe.get_doc('Address', name)
else:
address = frappe.new_doc('Address')
address.country = frappe.get_cached_value('Company', args.get('company'), 'country')
if args.get('company'):
address.country = frappe.get_cached_value('Company',
args.get('company'), 'country')
address.append('links', {
'link_doctype': 'Customer',
'link_name': customer
@@ -529,7 +541,6 @@ def make_address(args, customer):
address.flags.ignore_mandatory = True
address.save(ignore_permissions=True)
def make_email_queue(email_queue):
name_list = []
for key, data in iteritems(email_queue):
@@ -546,7 +557,6 @@ def make_email_queue(email_queue):
return name_list
def validate_item(doc):
for item in doc.get('items'):
if not frappe.db.exists('Item', item.get('item_code')):
@@ -565,7 +575,6 @@ def validate_item(doc):
item_doc.save(ignore_permissions=True)
frappe.db.commit()
def submit_invoice(si_doc, name, doc, name_list):
try:
si_doc.insert()
@@ -581,7 +590,6 @@ def submit_invoice(si_doc, name, doc, name_list):
return name_list
def save_invoice(doc, name, name_list):
try:
if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):

View File

@@ -44,6 +44,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
if (this.frm.doc.is_return) {
this.frm.return_print_format = "Sales Invoice Return";
}
this.show_general_ledger();
if(doc.update_stock) this.show_stock_ledger();
@@ -131,16 +135,24 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
set_default_print_format: function() {
// set default print format to POS type
// set default print format to POS type or Credit Note
if(cur_frm.doc.is_pos) {
if(cur_frm.pos_print_format) {
cur_frm.meta._default_print_format = cur_frm.meta.default_print_format;
cur_frm.meta.default_print_format = cur_frm.pos_print_format;
}
} else if(cur_frm.doc.is_return && !cur_frm.meta.default_print_format) {
if(cur_frm.return_print_format) {
cur_frm.meta._default_print_format = cur_frm.meta.default_print_format;
cur_frm.meta.default_print_format = cur_frm.return_print_format;
}
} else {
if(cur_frm.meta._default_print_format) {
cur_frm.meta.default_print_format = cur_frm.meta._default_print_format;
cur_frm.meta._default_print_format = null;
} else if(in_list([cur_frm.pos_print_format, cur_frm.return_print_format], cur_frm.meta.default_print_format)) {
cur_frm.meta.default_print_format = null;
cur_frm.meta._default_print_format = null;
}
}
},
@@ -174,9 +186,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
method: "erpnext.selling.doctype.quotation.quotation.make_sales_invoice",
source_doctype: "Quotation",
target: me.frm,
setters: {
customer: me.frm.doc.customer || undefined,
},
setters: [{
fieldtype: 'Link',
label: __('Customer'),
options: 'Customer',
fieldname: 'party_name',
default: me.frm.doc.customer,
}],
get_query_filters: {
docstatus: 1,
status: ["!=", "Lost"],

View File

@@ -78,6 +78,7 @@ class SalesInvoice(SellingController):
self.so_dn_required()
self.validate_proj_cust()
self.validate_pos_return()
self.validate_with_previous_doc()
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_uom_is_integer("uom", "qty")
@@ -166,6 +167,7 @@ class SalesInvoice(SellingController):
self.make_gl_entries()
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.check_credit_limit()
@@ -198,6 +200,16 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_submit")
def validate_pos_return(self):
if self.is_pos and self.is_return:
total_amount_in_payments = 0
for payment in self.payments:
total_amount_in_payments += payment.amount
invoice_total = self.rounded_total or self.grand_total
if flt(total_amount_in_payments, self.precision("grand_total")) < invoice_total:
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice."))
@@ -222,6 +234,7 @@ class SalesInvoice(SellingController):
self.update_billing_status_in_dn()
if not self.is_return:
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.update_serial_no(in_cancel=True)
@@ -397,14 +410,18 @@ class SalesInvoice(SellingController):
if pos.get('account_for_change_amount'):
self.account_for_change_amount = pos.get('account_for_change_amount')
for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name',
'company', 'select_print_heading', 'cash_bank_account', 'company_address',
'write_off_account', 'write_off_cost_center', 'apply_discount_on', 'cost_center'):
for fieldname in ('territory', 'naming_series', 'currency', 'letter_head', 'tc_name',
'company', 'select_print_heading', 'cash_bank_account', 'write_off_account',
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
if (not for_validate) or (for_validate and not self.get(fieldname)):
self.set(fieldname, pos.get(fieldname))
customer_price_list = frappe.get_value("Customer", self.customer, 'default_price_list')
for field in ['taxes_and_charges', 'company_address']:
if pos.get(field):
self.set(field, pos.get(field))
if not customer_price_list:
self.set('selling_price_list', pos.get('selling_price_list'))
@@ -785,10 +802,13 @@ class SalesInvoice(SellingController):
asset.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None)
else:
account_currency = get_account_currency(item.income_account)
income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict({
"account": item.income_account if not item.enable_deferred_revenue else item.deferred_revenue_account,
"account": income_account,
"against": self.customer,
"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))

View File

@@ -28,10 +28,12 @@ class TestSalesInvoice(unittest.TestCase):
w.submit()
return w
def setUp(self):
@classmethod
def setUpClass(self):
unlink_payment_on_cancel_of_invoice()
def tearDown(self):
@classmethod
def tearDownClass(self):
unlink_payment_on_cancel_of_invoice(0)
def test_timestamp_change(self):
@@ -134,6 +136,7 @@ class TestSalesInvoice(unittest.TestCase):
unlink_payment_on_cancel_of_invoice(0)
si = frappe.get_doc('Sales Invoice', si.name)
self.assertRaises(frappe.LinkExistsError, si.cancel)
unlink_payment_on_cancel_of_invoice()
def test_sales_invoice_calculation_export_currency(self):
si = frappe.copy_doc(test_records[2])

View File

@@ -9,11 +9,13 @@ from frappe.model.meta import get_field_precision
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
class ClosedAccountingPeriod(frappe.ValidationError): pass
class StockAccountInvalidTransaction(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
if gl_map:
if not cancel:
validate_accounting_period(gl_map)
gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1:
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
@@ -22,6 +24,27 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd
else:
delete_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
def validate_accounting_period(gl_map):
accounting_periods = frappe.db.sql(""" SELECT
ap.name as name
FROM
`tabAccounting Period` ap, `tabClosed Document` cd
WHERE
ap.name = cd.parent
AND ap.company = %(company)s
AND cd.closed = 1
AND cd.document_type = %(voucher_type)s
AND %(date)s between ap.start_date and ap.end_date
""", {
'date': gl_map[0].posting_date,
'company': gl_map[0].company,
'voucher_type': gl_map[0].voucher_type
}, as_dict=1)
if accounting_periods:
frappe.throw(_("You can't create accounting entries in the closed accounting period {0}")
.format(accounting_periods[0].name), ClosedAccountingPeriod)
def process_gl_map(gl_map, merge_entries=True):
if merge_entries:
gl_map = merge_similar_entries(gl_map)

View File

@@ -124,8 +124,6 @@ def check_matching_amount(bank_account, company, transaction):
'txt': '%%%s%%' % amount
}, as_dict=True)
frappe.errprint(journal_entries)
if transaction.credit > 0:
sales_invoices = frappe.db.sql("""
SELECT

View File

@@ -1625,7 +1625,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
setTimeout(function () {
w.print();
w.close();
}, 1000)
}, 1000);
},
submit_invoice: function () {
@@ -1682,6 +1682,12 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
$(this.wrapper).find('.pos-bill').css('pointer-events', pointer_events);
$(this.wrapper).find('.pos-items-section').css('pointer-events', pointer_events);
this.set_primary_action();
$(this.wrapper).find('#pos-item-disc').prop('disabled',
this.pos_profile_data.allow_user_to_edit_discount ? false : true);
$(this.wrapper).find('#pos-item-price').prop('disabled',
this.pos_profile_data.allow_user_to_edit_rate ? false : true);
},
create_invoice: function () {
@@ -1699,13 +1705,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if (this.frm.doc.offline_pos_name
&& in_list(existing_pos_list, this.frm.doc.offline_pos_name)) {
this.update_invoice()
//to retrieve and set the default payment
invoice_data[this.frm.doc.offline_pos_name] = this.frm.doc;
invoice_data[this.frm.doc.offline_pos_name].payments[0].amount = this.frm.doc.net_total
invoice_data[this.frm.doc.offline_pos_name].payments[0].base_amount = this.frm.doc.net_total
this.frm.doc.paid_amount = this.frm.doc.net_total
this.frm.doc.outstanding_amount = 0
} else if(!this.frm.doc.offline_pos_name) {
this.frm.doc.offline_pos_name = frappe.datetime.now_datetime();
this.frm.doc.posting_date = frappe.datetime.get_today();
@@ -1762,6 +1761,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.si_docs = this.get_submitted_invoice() || [];
this.email_queue_list = this.get_email_queue() || {};
this.customers_list = this.get_customers_details() || {};
if(this.customer_doc) {
this.freeze = this.customer_doc.display
}
@@ -1906,7 +1906,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
serial_no = me.item_serial_no[key][0];
}
if (this.items[0].has_serial_no && serial_no == "") {
if (this.items && this.items[0].has_serial_no && serial_no == "") {
this.refresh();
frappe.throw(__(repl("Error: Serial no is mandatory for item %(item)s", {
'item': this.items[0].item_code

View File

@@ -53,7 +53,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
set_other_values(out, party, party_type)
set_price_list(out, party, party_type, price_list, pos_profile)
out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, out.customer_group, out.supplier_type)
out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, out.customer_group, out.supplier_group)
if fetch_payment_terms_template:
out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
@@ -155,7 +155,7 @@ def set_price_list(out, party, party_type, given_price_list, pos=None):
# price list
price_list = get_permitted_documents('Price List')
if price_list:
if price_list and len(price_list) == 1:
price_list = price_list[0]
elif pos and party_type == 'Customer':
customer_price_list = frappe.get_value('Customer', party.name, 'default_price_list')
@@ -450,7 +450,9 @@ def get_timeline_data(doctype, name):
# fetch and append data from Activity Log
data += frappe.db.sql("""select {fields}
from `tabActivity Log`
where reference_doctype="{doctype}" and reference_name="{name}"
where (reference_doctype="{doctype}" and reference_name="{name}")
or (timeline_doctype in ("{doctype}") and timeline_name="{name}")
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name="{name}")
and status!='Success' and creation > {after}
{group_by} order by creation desc
""".format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields,
@@ -586,4 +588,4 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None):
.format(("credit") if party_type == "Customer" else "debit", cond) , party_type)
if data:
return frappe._dict(data)
return frappe._dict(data)

View File

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

View File

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

View File

@@ -73,6 +73,12 @@ frappe.query_reports["Accounts Payable"] = {
}
}
},
{
"fieldname":"payment_terms_template",
"label": __("Payment Terms Template"),
"fieldtype": "Link",
"options": "Payment Terms Template"
},
{
"fieldname":"supplier_group",
"label": __("Supplier Group"),

View File

@@ -63,6 +63,12 @@ frappe.query_reports["Accounts Payable Summary"] = {
"fieldtype": "Link",
"options": "Supplier"
},
{
"fieldname":"payment_terms_template",
"label": __("Payment Terms Template"),
"fieldtype": "Link",
"options": "Payment Terms Template"
},
{
"fieldname":"supplier_group",
"label": __("Supplier Group"),

View File

@@ -1,275 +1,269 @@
<style>
.print-format {
padding: 4mm;
font-size: 8.0pt !important;
}
.print-format td {
vertical-align:middle !important;
}
</style>
.print-format {
padding: 4mm;
font-size: 8.0pt !important;
}
.print-format td {
vertical-align:middle !important;
}
</style>
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
<h4 class="text-center">
{% if (filters.customer_name) { %}
{%= filters.customer_name %}
{% } else { %}
{%= filters.customer || filters.supplier %}
{% } %}
</h4>
<h6 class="text-center">
{% if (filters.tax_id) { %}
{%= __("Tax Id: ")%} {%= filters.tax_id %}
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
<h4 class="text-center">
{% if (filters.customer_name) { %}
{%= filters.customer_name %}
{% } else { %}
{%= filters.customer || filters.supplier %}
{% } %}
</h6>
<h5 class="text-center">
{%= __(filters.ageing_based_on) %}
{%= __("Until") %}
{%= frappe.datetime.str_to_user(filters.report_date) %}
</h5>
</h4>
<h6 class="text-center">
{% if (filters.tax_id) { %}
{%= __("Tax Id: ")%} {%= filters.tax_id %}
{% } %}
</h6>
<h5 class="text-center">
{%= __(filters.ageing_based_on) %}
{%= __("Until") %}
{%= frappe.datetime.str_to_user(filters.report_date) %}
</h5>
<div class="clearfix">
<div class="pull-left">
{% if(filters.payment_terms) { %}
<strong>{%= __("Payment Terms") %}:</strong> {%= filters.payment_terms %}
{% } %}
<div class="clearfix">
<div class="pull-left">
{% if(filters.payment_terms) { %}
<strong>{%= __("Payment Terms") %}:</strong> {%= filters.payment_terms %}
{% } %}
</div>
<div class="pull-right">
{% if(filters.credit_limit) { %}
<strong>{%= __("Credit Limit") %}:</strong> {%= format_currency(filters.credit_limit) %}
{% } %}
</div>
</div>
<div class="pull-right">
{% if(filters.credit_limit) { %}
<strong>{%= __("Credit Limit") %}:</strong> {%= format_currency(filters.credit_limit) %}
{% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop();
var range1 = report.columns[11].label;
var range2 = report.columns[12].label;
var range3 = report.columns[13].label;
var range4 = report.columns[14].label;
var range5 = report.columns[15].label;
%}
{% if(balance_row) { %}
<table class="table table-bordered table-condensed">
<caption class="text-right">(Amount in {%= data[0]["currency"] || "" %})</caption>
<colgroup>
<col style="width: 30mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
</colgroup>
<thead>
<tr>
<th>{%= __(" ") %}</th>
<th>{%= __(range1) %}</th>
<th>{%= __(range2) %}</th>
<th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th>
<th>{%= __("Total") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td>
<td class="text-right">{%= format_currency(balance_row["range2"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range3"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range4"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range5"]) %}</td>
<td class="text-right">
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
</td>
</tr>
<td>{%= __("Future Payments") %}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-right">
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
</td>
<tr class="cvs-footer">
<th class="text-left">{%= __("Cheques Required") %}</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th class="text-right">
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
</tr>
</tbody>
</table>
{% } %}
{% } %}
</div>
</div>
{% if(filters.show_pdc_in_print) { %}
{% var balance_row = data.slice(-1).pop();
var range1 = report.columns[11].label;
var range2 = report.columns[12].label;
var range3 = report.columns[13].label;
var range4 = report.columns[14].label;
var range5 = report.columns[15].label;
var range6 = report.columns[16].label;
%}
{% if(balance_row) { %}
<table class="table table-bordered table-condensed">
<caption class="text-right">(Amount in {%= data[0][__("currency")] || "" %})</caption>
<colgroup>
<col style="width: 30mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
<col style="width: 18mm;">
</colgroup>
<table class="table table-bordered">
<thead>
<tr>
<th>{%= __(" ") %}</th>
<th>{%= __(range1) %}</th>
<th>{%= __(range2) %}</th>
<th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th>
<th>{%= __(range6) %}</th>
<th>{%= __("Total") %}</th>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
<th style="width: 10%">{%= __("Date") %}</th>
<th style="width: 4%">{%= __("Age (Days)") %}</th>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<th style="width: 14%">{%= __("Reference") %}</th>
<th style="width: 10%">{%= __("Sales Person") %}</th>
{% } else { %}
<th style="width: 24%">{%= __("Reference") %}</th>
{% } %}
{% if(!filters.show_future_payments) { %}
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
{% if(!filters.show_future_payments) { %}
<th style="width: 10%; text-align: right">{%= __("Paid Amount") %}</th>
<th style="width: 10%; text-align: right">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 12%">{%= __("Customer LPO No.") %}</th>
{% } %}
<th style="width: 10%">{%= __("Future Payment Ref") %}</th>
<th style="width: 10%">{%= __("Future Payment Amount") %}</th>
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
{% } %}
{% } else { %}
<th style="width: 40%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
<th style="width: 15%">{%= __("Total Invoiced Amount") %}</th>
<th style="width: 15%">{%= __("Total Paid Amount") %}</th>
<th style="width: 15%">{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}</th>
<th style="width: 15%">{%= __("Total Outstanding Amount") %}</th>
{% } %}
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row[range1], null, 2) %}</td>
<td class="text-right">{%= format_currency(balance_row[range2]) %}</td>
<td class="text-right">{%= format_currency(balance_row[range3]) %}</td>
<td class="text-right">{%= format_currency(balance_row[range4]) %}</td>
<td class="text-right">{%= format_currency(balance_row[range5]) %}</td>
<td class="text-right">{%= format_currency(balance_row[range6]) %}</td>
<td class="text-right">
{%= format_currency(flt(balance_row[("outstanding_amount")]), data[data.length-1]["currency"]) %}
</td>
</tr>
<td>{%= __("PDC/LC") %}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-right">
{%= format_currency(flt(balance_row[("pdc/lc_amount")]), data[data.length-1]["currency"]) %}
</td>
<tr class="cvs-footer">
<th class="text-left">{%= __("Cheques Required") %}</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th class="text-right">
{%= format_currency(flt(balance_row[("outstanding_amount")]-balance_row[("pdc/lc_amount")]), data[data.length-1]["currency"]) %}</th>
</tr>
</tbody>
</table>
{% } %}
{% } %}
<table class="table table-bordered">
<thead>
<tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
<th style="width: 10%">{%= __("Date") %}</th>
<th style="width: 4%">{%= __("Age (Days)") %}</th>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %}
<th style="width: 14%">{%= __("Reference") %}</th>
<th style="width: 10%">{%= __("Sales Person") %}</th>
{% } else { %}
<th style="width: 24%">{%= __("Reference") %}</th>
{% } %}
{% if(!filters.show_pdc_in_print) { %}
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
{% if(!filters.show_pdc_in_print) { %}
<th style="width: 10%; text-align: right">{%= __("Paid Amount") %}</th>
<th style="width: 10%; text-align: right">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th>
{% if(filters.show_pdc_in_print) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 12%">{%= __("Customer LPO No.") %}</th>
{% } %}
<th style="width: 10%">{%= __("PDC/LC Ref") %}</th>
<th style="width: 10%">{%= __("PDC/LC Amount") %}</th>
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
{% } %}
{% } else { %}
<th style="width: 40%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
<th style="width: 15%">{%= __("Total Invoiced Amount") %}</th>
<th style="width: 15%">{%= __("Total Paid Amount") %}</th>
<th style="width: 15%">{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}</th>
<th style="width: 15%">{%= __("Total Outstanding Amount") %}</th>
{% } %}
</tr>
</thead>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
{% if(data[i][__("Customer")] || data[i][__("Supplier")]) { %}
<td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td style="text-align: right">{%= data[i][__("Age (Days)")] %}</td>
<td>
{% if(!filters.show_pdc_in_print) { %}
{%= data[i]["voucher_type"] %}
<br>
{% } %}
{%= data[i]["voucher_no"] %}
</td>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %}
<td>{%= data[i]["sales_person"] %}</td>
{% } %}
{% if(!filters.show_pdc_in_print) { %}
<td>
{% if(!(filters.customer || filters.supplier)) { %}
{%= data[i][__("Customer")] || data[i][__("Supplier")] %}
{% if(data[i][__("Customer Name")] && data[i][__("Customer Name")] != data[i][__("Customer")]) { %}
<br> {%= data[i][__("Customer Name")] %}
{% } else if(data[i][__("Supplier Name")] != data[i][__("Supplier")]) { %}
<br> {%= data[i][__("Supplier Name")] %}
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
{% if(data[i]["party"]) { %}
<td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td style="text-align: right">{%= data[i]["age"] %}</td>
<td>
{% if(!filters.show_future_payments) { %}
{%= data[i]["voucher_type"] %}
<br>
{% } %}
{%= data[i]["voucher_no"] %}
</td>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<td>{%= data[i]["sales_person"] %}</td>
{% } %}
<div>
{% if data[i][__("Remarks")] %}
{%= __("Remarks") %}:
{%= data[i][__("Remarks")] %}
{% } %}
</div>
</td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["invoiced_amount"], data[i]["currency"]) %}</td>
{% if(!filters.show_pdc_in_print) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">
{%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %}</td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td>
{% if(filters.show_pdc_in_print) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right">
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i][("pdc/lc_ref")] %}</td>
<td style="text-align: right">{%= format_currency(data[i][("pdc/lc_amount")], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i][("remaining_balance")], data[i]["currency"]) %}</td>
{% } %}
{% } else { %}
<td></td>
{% if(!filters.show_pdc_in_print) { %}
<td></td>
{% } %}
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %}
<td></td>
{% } %}
<td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right">
{%= format_currency(data[i]["invoiced_amount"], data[i]["currency"] ) %}</td>
{% if(!filters.show_pdc_in_print) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %} </td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td>
{% if(filters.show_pdc_in_print) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right">
{%= data[i][__("Customer LPO")] %}</td>
{% } %}
<td style="text-align: right">{%= data[i][("pdc/lc_ref")] %}</td>
<td style="text-align: right">{%= format_currency(data[i][("pdc/lc_amount")], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i][("remaining_balance")], data[i]["currency"]) %}</td>
{% } %}
{% } %}
{% } else { %}
{% if(data[i][__("Customer")] || data[i][__("Supplier")]|| "&nbsp;") { %}
{% if((data[i][__("Customer")] || data[i][__("Supplier")]) != __("'Total'")) { %}
{% if(!filters.show_future_payments) { %}
<td>
{% if(!(filters.customer || filters.supplier)) { %}
{%= data[i][__("Customer")] || data[i][__("Supplier")] %}
{% if(data[i][__("Customer Name")] && data[i][__("Customer Name")] != data[i][__("Customer")]) { %}
<br> {%= data[i][__("Customer Name")] %}
{% } else if(data[i][__("Supplier Name")] != data[i][__("Supplier")]) { %}
<br> {%= data[i][__("Supplier Name")] %}
{%= data[i]["party"] %}
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i]["customer_name"] %}
{% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
<br> {%= data[i]["supplier_name"] %}
{% } %}
{% } %}
<br>{%= __("Remarks") %}:
{%= data[i][__("Remarks")] %}
<div>
{% if data[i]["remarks"] %}
{%= __("Remarks") %}:
{%= data[i]["remarks"] %}
{% } %}
</div>
</td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">
{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right">
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %}
{% } else { %}
<td><b>{%= __("Total") %}</b></td>
<td></td>
{% if(!filters.show_future_payments) { %}
<td></td>
{% } %}
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<td></td>
{% } %}
<td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right">
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
{% } else { %}
{% if(data[i]["party"]|| "&nbsp;") { %}
{% if((data[i]["party"]) != __("'Total'")) { %}
<td>
{% if(!(filters.customer || filters.supplier)) { %}
{%= data[i]["party"] %}
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i]["customer_name"] %}
{% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
<br> {%= data[i]["supplier_name"] %}
{% } %}
{% } %}
<br>{%= __("Remarks") %}:
{%= data[i]["remarks"] %}
</td>
{% } else { %}
<td><b>{%= __("Total") %}</b></td>
{% } %}
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% } %}
<td style="text-align: right">{%= format_currency(data[i][("total_invoiced_amt")], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i][("total_paid_amt")], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= report.report_name === "Accounts Receivable Summary" ? format_currency(data[i][__("credit_note_amt")], data[i]["currency"]) : format_currency(data[i][__("debit_note_amt")], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i][("total_outstanding_amt")], data[i]["currency"]) %}</td>
{% } %}
</tr>
{% } %}
</tr>
{% } %}
</tbody>
</table>
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
</tbody>
</table>
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>

View File

@@ -115,13 +115,18 @@ frappe.query_reports["Accounts Receivable"] = {
"fieldtype": "Check",
},
{
"fieldname":"show_pdc_in_print",
"label": __("Show PDC in Print"),
"fieldname":"show_future_payments",
"label": __("Show Future Payments"),
"fieldtype": "Check",
},
{
"fieldname":"show_sales_person_in_print",
"label": __("Show Sales Person in Print"),
"fieldname":"show_delivery_notes",
"label": __("Show Delivery Notes"),
"fieldtype": "Check",
},
{
"fieldname":"show_sales_person",
"label": __("Show Sales Person"),
"fieldtype": "Check",
},
{

View File

@@ -14,33 +14,44 @@ class TestAccountsReceivable(unittest.TestCase):
filters = {
'company': '_Test Company 2',
'based_on_payment_terms': 1
'based_on_payment_terms': 1,
'report_date': today(),
'range1': 30,
'range2': 60,
'range3': 90,
'range4': 120
}
# check invoice grand total and invoiced column's value for 3 payment terms
name = make_sales_invoice()
report = execute(filters)
expected_data = [[100,30], [100,50], [100,20]]
expected_data = [[100, 30], [100, 50], [100, 20]]
self.assertEqual(expected_data[0], report[1][0][7:9])
self.assertEqual(expected_data[1], report[1][1][7:9])
self.assertEqual(expected_data[2], report[1][2][7:9])
for i in range(3):
row = report[1][i-1]
self.assertEqual(expected_data[i-1], [row.invoice_grand_total, row.invoiced])
# check invoice grand total, invoiced, paid and outstanding column's value after payment
make_payment(name)
report = execute(filters)
expected_data_after_payment = [[100,50], [100,20]]
expected_data_after_payment = [[100, 50, 10, 40], [100, 20, 0, 20]]
self.assertEqual(expected_data_after_payment[0], report[1][0][7:9])
self.assertEqual(expected_data_after_payment[1], report[1][1][7:9])
for i in range(2):
row = report[1][i-1]
self.assertEqual(expected_data_after_payment[i-1],
[row.invoice_grand_total, row.invoiced, row.paid, row.outstanding])
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
make_credit_note(name)
report = execute(filters)
expected_data_after_credit_note = [[100,100,30,100,-30]]
self.assertEqual(expected_data_after_credit_note[0], report[1][0][7:12])
expected_data_after_credit_note = [100, 0, 0, 40, -40]
row = report[1][0]
self.assertEqual(expected_data_after_credit_note,
[row.invoice_grand_total, row.invoiced, row.paid, row.credit_note, row.outstanding])
def make_sales_invoice():
frappe.set_user("Administrator")
@@ -64,7 +75,7 @@ def make_sales_invoice():
return si.name
def make_payment(docname):
pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=30)
pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=40)
pe.paid_from = "Debtors - _TC2"
pe.insert()
pe.submit()

View File

@@ -3,236 +3,11 @@
from __future__ import unicode_literals
import frappe
from frappe import _, scrub
from frappe.utils import flt
from frappe import _
from frappe.utils import flt, cint
from erpnext.accounts.party import get_partywise_advanced_payment_amount
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
from six import iteritems
from six.moves import zip
class AccountsReceivableSummary(ReceivablePayableReport):
def run(self, args):
party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
return self.get_columns(party_naming_by, args), self.get_data(party_naming_by, args)
def get_columns(self, party_naming_by, args):
columns = [_(args.get("party_type")) + ":Link/" + args.get("party_type") + ":200"]
if party_naming_by == "Naming Series":
columns += [ args.get("party_type") + " Name::140"]
credit_debit_label = "Credit Note Amt" if args.get('party_type') == 'Customer' else "Debit Note Amt"
columns += [{
"label": _("Advance Amount"),
"fieldname": "advance_amount",
"fieldtype": "Currency",
"options": "currency",
"width": 100
},{
"label": _("Total Invoiced Amt"),
"fieldname": "total_invoiced_amt",
"fieldtype": "Currency",
"options": "currency",
"width": 100
},
{
"label": _("Total Paid Amt"),
"fieldname": "total_paid_amt",
"fieldtype": "Currency",
"options": "currency",
"width": 100
}]
columns += [
{
"label": _(credit_debit_label),
"fieldname": scrub(credit_debit_label),
"fieldtype": "Currency",
"options": "currency",
"width": 140
},
{
"label": _("Total Outstanding Amt"),
"fieldname": "total_outstanding_amt",
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _("0-" + str(self.filters.range1)),
"fieldname": scrub("0-" + str(self.filters.range1)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range1) + "-" + str(self.filters.range2)),
"fieldname": scrub(str(self.filters.range1) + "-" + str(self.filters.range2)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range2) + "-" + str(self.filters.range3)),
"fieldname": scrub(str(self.filters.range2) + "-" + str(self.filters.range3)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range3) + "-" + str(self.filters.range4)),
"fieldname": scrub(str(self.filters.range3) + "-" + str(self.filters.range4)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range4) + _("-Above")),
"fieldname": scrub(str(self.filters.range4) + _("-Above")),
"fieldtype": "Currency",
"options": "currency",
"width": 160
}
]
if args.get("party_type") == "Customer":
columns += [{
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory",
"width": 80
},
{
"label": _("Customer Group"),
"fieldname": "customer_group",
"fieldtype": "Link",
"options": "Customer Group",
"width": 80
},
{
"label": _("Sales Person"),
"fieldtype": "Data",
"fieldname": "sales_person",
"width": 120,
}]
if args.get("party_type") == "Supplier":
columns += [{
"label": _("Supplier Group"),
"fieldname": "supplier_group",
"fieldtype": "Link",
"options": "Supplier Group",
"width": 80
}]
columns.append({
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"width": 80
})
return columns
def get_data(self, party_naming_by, args):
data = []
partywise_total = self.get_partywise_total(party_naming_by, args)
partywise_advance_amount = get_partywise_advanced_payment_amount(args.get("party_type"),
self.filters.get("report_date")) or {}
for party, party_dict in iteritems(partywise_total):
row = [party]
if party_naming_by == "Naming Series":
row += [self.get_party_name(args.get("party_type"), party)]
row += [partywise_advance_amount.get(party, 0)]
paid_amt = 0
if party_dict.paid_amt > 0:
paid_amt = flt(party_dict.paid_amt - partywise_advance_amount.get(party, 0))
row += [
party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt,
party_dict.range1, party_dict.range2, party_dict.range3, party_dict.range4, party_dict.range5
]
if args.get("party_type") == "Customer":
row += [self.get_territory(party), self.get_customer_group(party), ", ".join(set(party_dict.sales_person))]
if args.get("party_type") == "Supplier":
row += [self.get_supplier_group(party)]
row.append(party_dict.currency)
data.append(row)
return data
def get_partywise_total(self, party_naming_by, args):
party_total = frappe._dict()
for d in self.get_voucherwise_data(party_naming_by, args):
party_total.setdefault(d.party,
frappe._dict({
"invoiced_amt": 0,
"paid_amt": 0,
"credit_amt": 0,
"outstanding_amt": 0,
"range1": 0,
"range2": 0,
"range3": 0,
"range4": 0,
"range5": 0,
"sales_person": []
})
)
for k in list(party_total[d.party]):
if k not in ["currency", "sales_person"]:
party_total[d.party][k] += flt(d.get(k, 0))
party_total[d.party].currency = d.currency
if d.sales_person:
party_total[d.party].sales_person.append(d.sales_person)
return party_total
def get_voucherwise_data(self, party_naming_by, args):
voucherwise_data = ReceivablePayableReport(self.filters).run(args)[1]
cols = ["posting_date", "party"]
if party_naming_by == "Naming Series":
cols += ["party_name"]
if args.get("party_type") == 'Customer':
cols += ["contact"]
cols += ["voucher_type", "voucher_no", "due_date"]
if args.get("party_type") == "Supplier":
cols += ["bill_no", "bill_date"]
cols += ["invoiced_amt", "paid_amt", "credit_amt",
"outstanding_amt", "age", "range1", "range2", "range3", "range4", "range5", "currency", "pdc/lc_date", "pdc/lc_ref",
"pdc/lc_amount"]
if args.get("party_type") == "Supplier":
cols += ["supplier_group", "remarks"]
if args.get("party_type") == "Customer":
cols += ["po_no", "do_no", "territory", "customer_group", "sales_person", "remarks"]
return self.make_data_dict(cols, voucherwise_data)
def make_data_dict(self, cols, data):
data_dict = []
for d in data:
data_dict.append(frappe._dict(zip(cols, d)))
return data_dict
def execute(filters=None):
args = {
@@ -241,3 +16,120 @@ def execute(filters=None):
}
return AccountsReceivableSummary(filters).run(args)
class AccountsReceivableSummary(ReceivablePayableReport):
def run(self, args):
self.party_type = args.get('party_type')
self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
self.get_columns()
self.get_data(args)
return self.columns, self.data
def get_data(self, args):
self.data = []
self.receivables = ReceivablePayableReport(self.filters).run(args)[1]
self.get_party_total(args)
party_advance_amount = get_partywise_advanced_payment_amount(self.party_type,
self.filters.report_date) or {}
for party, party_dict in iteritems(self.party_total):
row = frappe._dict()
row.party = party
if self.party_naming_by == "Naming Series":
row.party_name = frappe.get_cached_value(self.party_type,
party, frappe.scrub(self.party_type) + "_name")
row.update(party_dict)
# Advance against party
row.advance = party_advance_amount.get(party, 0)
# In AR/AP, advance shown in paid columns,
# but in summary report advance shown in separate column
row.paid -= row.advance
self.data.append(row)
def get_party_total(self, args):
self.party_total = frappe._dict()
for d in self.receivables:
self.init_party_total(d)
# Add all amount columns
for k in list(self.party_total[d.party]):
if k not in ["currency", "sales_person"]:
self.party_total[d.party][k] += d.get(k, 0.0)
# set territory, customer_group, sales person etc
self.set_party_details(d)
def init_party_total(self, row):
self.party_total.setdefault(row.party, frappe._dict({
"invoiced": 0.0,
"paid": 0.0,
"credit_note": 0.0,
"outstanding": 0.0,
"range1": 0.0,
"range2": 0.0,
"range3": 0.0,
"range4": 0.0,
"range5": 0.0,
"sales_person": []
}))
def set_party_details(self, row):
self.party_total[row.party].currency = row.currency
for key in ('territory', 'customer_group', 'supplier_group'):
if row.get(key):
self.party_total[row.party][key] = row.get(key)
if row.sales_person:
self.party_total[row.party].sales_person.append(row.sales_person)
def get_columns(self):
self.columns = []
self.add_column(label=_(self.party_type), fieldname='party',
fieldtype='Link', options=self.party_type, width=180)
if self.party_naming_by == "Naming Series":
self.add_column(_('{0} Name').format(self.party_type),
fieldname = 'party_name', fieldtype='Data')
credit_debit_label = "Credit Note" if self.party_type == 'Customer' else "Debit Note"
self.add_column(_('Advance Amount'), fieldname='advance')
self.add_column(_('Invoiced Amount'), fieldname='invoiced')
self.add_column(_('Paid Amount'), fieldname='paid')
self.add_column(_(credit_debit_label), fieldname='credit_note')
self.add_column(_('Outstanding Amount'), fieldname='outstanding')
self.setup_ageing_columns()
if self.party_type == "Customer":
self.add_column(label=_('Territory'), fieldname='territory', fieldtype='Link',
options='Territory')
self.add_column(label=_('Customer Group'), fieldname='customer_group', fieldtype='Link',
options='Customer Group')
if self.filters.show_sales_person:
self.add_column(label=_('Sales Person'), fieldname='sales_person', fieldtype='Data')
else:
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
options='Supplier Group')
self.add_column(label=_('Currency'), fieldname='currency', fieldtype='Link',
options='Currency', width=80)
def setup_ageing_columns(self):
for i, label in enumerate(["0-{range1}".format(range1=self.filters["range1"]),
"{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]),
"{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]),
"{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
"{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]):
self.add_column(label=label, fieldname='range' + str(i+1))

View File

@@ -135,11 +135,11 @@ def get_chart_data(filters, columns, asset, liability, equity):
datasets = []
if asset_data:
datasets.append({'name':'Assets', 'values': asset_data})
datasets.append({'name':_('Assets'), 'values': asset_data})
if liability_data:
datasets.append({'name':'Liabilities', 'values': liability_data})
datasets.append({'name':_('Liabilities'), 'values': liability_data})
if equity_data:
datasets.append({'name':'Equity', 'values': equity_data})
datasets.append({'name':_('Equity'), 'values': equity_data})
chart = {
"data": {
@@ -153,4 +153,4 @@ def get_chart_data(filters, columns, asset, liability, equity):
else:
chart["type"] = "line"
return chart
return chart

View File

@@ -12,11 +12,11 @@ def execute(filters=None):
columns = get_columns()
if not filters.get("account"): return columns, []
account_currency = frappe.db.get_value("Account", filters.account, "account_currency")
data = get_entries(filters)
from erpnext.accounts.utils import get_balance_on
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
@@ -24,7 +24,7 @@ def execute(filters=None):
for d in data:
total_debit += flt(d.debit)
total_credit += flt(d.credit)
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
bank_bal = flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) \
@@ -39,7 +39,7 @@ def execute(filters=None):
"credit": total_credit,
"account_currency": account_currency
},
get_balance_row(_("Cheques and Deposits incorrectly cleared"), amounts_not_reflected_in_system,
get_balance_row(_("Cheques and Deposits incorrectly cleared"), amounts_not_reflected_in_system,
account_currency),
{},
get_balance_row(_("Calculated Bank Statement balance"), bank_bal, account_currency)
@@ -55,9 +55,15 @@ def get_columns():
"fieldtype": "Date",
"width": 90
},
{
"fieldname": "payment_document",
"label": _("Payment Document Type"),
"fieldtype": "Data",
"width": 220
},
{
"fieldname": "payment_entry",
"label": _("Payment Entry"),
"label": _("Payment Document"),
"fieldtype": "Dynamic Link",
"options": "payment_document",
"width": 220
@@ -100,7 +106,7 @@ def get_columns():
"label": _("Clearance Date"),
"fieldtype": "Date",
"width": 110
},
},
{
"fieldname": "account_currency",
"label": _("Currency"),
@@ -112,9 +118,9 @@ def get_columns():
def get_entries(filters):
journal_entries = frappe.db.sql("""
select "Journal Entry" as payment_document, jv.posting_date,
jv.name as payment_entry, jvd.debit_in_account_currency as debit,
jvd.credit_in_account_currency as credit, jvd.against_account,
select "Journal Entry" as payment_document, jv.posting_date,
jv.name as payment_entry, jvd.debit_in_account_currency as debit,
jvd.credit_in_account_currency as credit, jvd.against_account,
jv.cheque_no as reference_no, jv.cheque_date as ref_date, jv.clearance_date, jvd.account_currency
from
`tabJournal Entry Account` jvd, `tabJournal Entry` jv
@@ -122,13 +128,13 @@ def get_entries(filters):
and jvd.account = %(account)s and jv.posting_date <= %(report_date)s
and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s
and ifnull(jv.is_opening, 'No') = 'No'""", filters, as_dict=1)
payment_entries = frappe.db.sql("""
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no, reference_date as ref_date,
if(paid_to=%(account)s, received_amount, 0) as debit,
if(paid_from=%(account)s, paid_amount, 0) as credit,
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no, reference_date as ref_date,
if(paid_to=%(account)s, received_amount, 0) as debit,
if(paid_from=%(account)s, paid_amount, 0) as credit,
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
from `tabPayment Entry`
@@ -156,25 +162,25 @@ def get_entries(filters):
return sorted(list(payment_entries)+list(journal_entries+list(pos_entries)),
key=lambda k: k['posting_date'] or getdate(nowdate()))
def get_amounts_not_reflected_in_system(filters):
je_amount = frappe.db.sql("""
select sum(jvd.debit_in_account_currency - jvd.credit_in_account_currency)
from `tabJournal Entry Account` jvd, `tabJournal Entry` jv
where jvd.parent = jv.name and jv.docstatus=1 and jvd.account=%(account)s
and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s
and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s
and ifnull(jv.is_opening, 'No') = 'No' """, filters)
je_amount = flt(je_amount[0][0]) if je_amount else 0.0
pe_amount = frappe.db.sql("""
select sum(if(paid_from=%(account)s, paid_amount, received_amount))
from `tabPayment Entry`
where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date > %(report_date)s and clearance_date <= %(report_date)s""", filters)
pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0
return je_amount + pe_amount
def get_balance_row(label, amount, account_currency):

View File

@@ -130,7 +130,7 @@ def get_cash_flow_data(fiscal_year, companies, filters):
section_data.append(net_profit_loss)
for account in cash_flow_account['account_types']:
account_data = get_account_type_based_data(account['account_type'], companies, fiscal_year)
account_data = get_account_type_based_data(account['account_type'], companies, fiscal_year, filters)
account_data.update({
"account_name": account['label'],
"account": account['label'],
@@ -148,12 +148,12 @@ def get_cash_flow_data(fiscal_year, companies, filters):
return data
def get_account_type_based_data(account_type, companies, fiscal_year):
def get_account_type_based_data(account_type, companies, fiscal_year, filters):
data = {}
total = 0
for company in companies:
amount = get_account_type_based_gl_data(company,
fiscal_year.year_start_date, fiscal_year.year_end_date, account_type)
fiscal_year.year_start_date, fiscal_year.year_end_date, account_type, filters)
if amount and account_type == "Depreciation":
amount *= -1

View File

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

View File

@@ -10,6 +10,7 @@ from frappe import _, _dict
from erpnext.accounts.utils import get_account_currency
from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
from six import iteritems
from collections import OrderedDict
def execute(filters=None):
if not filters:
@@ -120,19 +121,11 @@ def get_gl_entries(filters):
select_fields = """, debit, credit, debit_in_account_currency,
credit_in_account_currency """
group_by_statement = ''
order_by_statement = "order by posting_date, account"
if filters.get("group_by") == _("Group by Voucher"):
order_by_statement = "order by posting_date, voucher_type, voucher_no"
if filters.get("group_by") == _("Group by Voucher (Consolidated)"):
group_by_statement = "group by voucher_type, voucher_no, account, cost_center"
select_fields = """, sum(debit) as debit, sum(credit) as credit,
sum(debit_in_account_currency) as debit_in_account_currency,
sum(credit_in_account_currency) as credit_in_account_currency"""
if filters.get("include_default_book_entries"):
filters['company_fb'] = frappe.db.get_value("Company",
filters.get("company"), 'default_finance_book')
@@ -145,11 +138,10 @@ def get_gl_entries(filters):
against_voucher_type, against_voucher, account_currency,
remarks, against, is_opening {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions} {group_by_statement}
where company=%(company)s {conditions}
{order_by_statement}
""".format(
select_fields=select_fields, conditions=get_conditions(filters),
group_by_statement=group_by_statement,
order_by_statement=order_by_statement
),
filters, as_dict=1)
@@ -186,7 +178,8 @@ def get_conditions(filters):
if not (filters.get("account") or filters.get("party") or
filters.get("group_by") in ["Group by Account", "Group by Party"]):
conditions.append("posting_date >=%(from_date)s")
conditions.append("posting_date <=%(to_date)s")
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
if filters.get("project"):
conditions.append("project in %(project)s")
@@ -269,7 +262,7 @@ def group_by_field(group_by):
return 'voucher_no'
def initialize_gle_map(gl_entries, filters):
gle_map = frappe._dict()
gle_map = OrderedDict()
group_by = group_by_field(filters.get('group_by'))
for gle in gl_entries:
@@ -280,6 +273,7 @@ def initialize_gle_map(gl_entries, filters):
def get_accountwise_gle(filters, gl_entries, gle_map):
totals = get_totals_dict()
entries = []
consolidated_gle = OrderedDict()
group_by = group_by_field(filters.get('group_by'))
def update_value_in_dict(data, key, gle):
@@ -305,11 +299,19 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
if filters.get("group_by") != _('Group by Voucher (Consolidated)'):
gle_map[gle.get(group_by)].entries.append(gle)
else:
entries.append(gle)
key = (gle.get("voucher_type"), gle.get("voucher_no"),
gle.get("account"), gle.get("cost_center"))
if key not in consolidated_gle:
consolidated_gle.setdefault(key, gle)
else:
update_value_in_dict(consolidated_gle, key, gle)
update_value_in_dict(gle_map[gle.get(group_by)].totals, 'closing', gle)
update_value_in_dict(totals, 'closing', gle)
for key, value in consolidated_gle.items():
entries.append(value)
return totals, entries
def get_result_as_list(data, filters):

View File

@@ -17,7 +17,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
filters.update({"from_date": filters.get("date_range") and filters.get("date_range")[0], "to_date": filters.get("date_range") and filters.get("date_range")[1]})
columns = get_columns(additional_table_columns)
company_currency = erpnext.get_company_currency(filters.get('company'))
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
item_list = get_items(filters, additional_query_columns)
if item_list:

View File

@@ -27,8 +27,8 @@ frappe.query_reports["Payment Period Based On Invoice Date"] = {
fieldname:"payment_type",
label: __("Payment Type"),
fieldtype: "Select",
options: "Incoming\nOutgoing",
default: "Incoming"
options: __("Incoming")+"\n"+__("Outgoing"),
default: __("Incoming")
},
{
"fieldname":"party_type",

View File

@@ -4,11 +4,13 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext.accounts.report.accounts_receivable.accounts_receivable import get_ageing_data
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
from frappe.utils import getdate, flt
def execute(filters=None):
if not filters: filters = {}
if not filters:
filters = {}
validate_filters(filters)
columns = get_columns(filters)
@@ -19,18 +21,29 @@ def execute(filters=None):
for d in entries:
invoice = invoice_details.get(d.against_voucher) or frappe._dict()
if d.reference_type=="Purchase Invoice":
if d.reference_type == "Purchase Invoice":
payment_amount = flt(d.debit) or -1 * flt(d.credit)
else:
payment_amount = flt(d.credit) or -1 * flt(d.debit)
row = [d.voucher_type, d.voucher_no, d.party_type, d.party, d.posting_date, d.against_voucher,
invoice.posting_date, invoice.due_date, d.debit, d.credit, d.remarks]
d.update({
"range1": 0,
"range2": 0,
"range3": 0,
"range4": 0,
"outstanding": payment_amount
})
if d.against_voucher:
row += get_ageing_data(30, 60, 90, 120, d.posting_date, invoice.posting_date, payment_amount)
else:
row += ["", "", "", "", ""]
ReceivablePayableReport(filters).get_ageing_data(invoice.posting_date, d)
row = [
d.voucher_type, d.voucher_no, d.party_type, d.party, d.posting_date, d.against_voucher,
invoice.posting_date, invoice.due_date, d.debit, d.credit, d.remarks,
d.age, d.range1, d.range2, d.range3, d.range4
]
if invoice.due_date:
row.append((getdate(d.posting_date) - getdate(invoice.due_date)).days or 0)
@@ -39,8 +52,8 @@ def execute(filters=None):
return columns, data
def validate_filters(filters):
if (filters.get("payment_type") == "Incoming" and filters.get("party_type") == "Supplier") or \
(filters.get("payment_type") == "Outgoing" and filters.get("party_type") == "Customer"):
if (filters.get("payment_type") == _("Incoming") and filters.get("party_type") == "Supplier") or \
(filters.get("payment_type") == _("Outgoing") and filters.get("party_type") == "Customer"):
frappe.throw(_("{0} payment entries can not be filtered by {1}")\
.format(filters.payment_type, filters.party_type))
@@ -51,7 +64,7 @@ def get_columns(filters):
_("Party Type") + "::100",
_("Party") + ":Dynamic Link/Party Type:140",
_("Posting Date") + ":Date:100",
_("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == "Outgoing" else ":Link/Sales Invoice:130"),
_("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"),
_("Invoice Posting Date") + ":Date:130",
_("Payment Due Date") + ":Date:130",
_("Debit") + ":Currency:120",
@@ -69,7 +82,7 @@ def get_conditions(filters):
conditions = []
if not filters.party_type:
if filters.payment_type == "Outgoing":
if filters.payment_type == _("Outgoing"):
filters.party_type = "Supplier"
else:
filters.party_type = "Customer"
@@ -101,7 +114,7 @@ def get_entries(filters):
def get_invoice_posting_date_map(filters):
invoice_details = {}
dt = "Sales Invoice" if filters.get("payment_type") == "Incoming" else "Purchase Invoice"
dt = "Sales Invoice" if filters.get("payment_type") == _("Incoming") else "Purchase Invoice"
for t in frappe.db.sql("select name, posting_date, due_date from `tab{0}`".format(dt), as_dict=1):
invoice_details[t.name] = t

View File

@@ -93,4 +93,6 @@ def get_chart_data(filters, columns, income, expense, net_profit_loss):
else:
chart["type"] = "line"
chart["fieldtype"] = "Currency"
return chart

View File

@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import flt
from frappe.model.meta import get_field_precision
from frappe import msgprint, _
def execute(filters=None):
@@ -67,8 +68,8 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
total_tax = 0
for tax_acc in tax_accounts:
if tax_acc not in income_accounts:
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc))
total_tax += tax_amount
tax_amount_precision = get_field_precision(frappe.get_meta("Sales Taxes and Charges").get_field("tax_amount"), currency=company_currency) or 2
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc), tax_amount_precision)
row.append(tax_amount)
# total tax, grand total, outstanding amount & rounded total

View File

@@ -5,9 +5,8 @@
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"letter_head": "Capital Traders",
"modified": "2018-12-12 05:10:02.987274",
"is_standard": "Yes",
"modified": "2019-02-12 05:10:02.987274",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Supplier Ledger Summary",

View File

@@ -6,8 +6,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"letter_head": "Gadgets International",
"modified": "2018-08-21 11:25:00.551823",
"modified": "2018-09-21 11:25:00.551823",
"modified_by": "Administrator",
"module": "Accounts",
"name": "TDS Computation Summary",

View File

@@ -6,8 +6,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"letter_head": "Gadgets International",
"modified": "2018-08-21 11:33:40.804532",
"modified": "2019-09-24 13:46:16.473711",
"modified_by": "Administrator",
"module": "Accounts",
"name": "TDS Payable Monthly",

View File

@@ -84,7 +84,8 @@ def validate_fiscal_year(date, fiscal_year, company, label="Date", doc=None):
throw(_("{0} '{1}' not in Fiscal Year {2}").format(label, formatdate(date), fiscal_year))
@frappe.whitelist()
def get_balance_on(account=None, date=None, party_type=None, party=None, company=None, in_account_currency=True, cost_center=None):
def get_balance_on(account=None, date=None, party_type=None, party=None, company=None,
in_account_currency=True, cost_center=None, ignore_account_permission=False):
if not account and frappe.form_dict.get("account"):
account = frappe.form_dict.get("account")
if not date and frappe.form_dict.get("date"):
@@ -104,6 +105,9 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
# get balance of all entries that exist
date = nowdate()
if account:
acc = frappe.get_doc("Account", account)
try:
year_start_date = get_fiscal_year(date, verbose=0)[1]
except FiscalYearError:
@@ -118,7 +122,12 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
allow_cost_center_in_entry_of_bs_account = get_allow_cost_center_in_entry_of_bs_account()
if cost_center and allow_cost_center_in_entry_of_bs_account:
if account:
report_type = acc.report_type
else:
report_type = ""
if cost_center and (allow_cost_center_in_entry_of_bs_account or report_type =='Profit and Loss'):
cc = frappe.get_doc("Cost Center", cost_center)
if cc.is_group:
cond.append(""" exists (
@@ -132,20 +141,14 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
if account:
acc = frappe.get_doc("Account", account)
if not frappe.flags.ignore_account_permission:
if not (frappe.flags.ignore_account_permission
or ignore_account_permission):
acc.check_permission("read")
if not allow_cost_center_in_entry_of_bs_account and acc.report_type == 'Profit and Loss':
if report_type == 'Profit and Loss':
# for pl accounts, get balance within a fiscal year
cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
% year_start_date)
elif allow_cost_center_in_entry_of_bs_account:
# for all accounts, get balance within a fiscal year if maintain cost center in balance account is checked
cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
% year_start_date)
# different filter for group and ledger - improved performance
if acc.is_group:
cond.append("""exists (
@@ -732,7 +735,7 @@ def get_children(doctype, parent, company, is_root=False):
filters.append(['company', '=', company])
else:
fields += ['account_currency'] if doctype == 'Account' else []
fields += ['root_type', 'account_currency'] if doctype == 'Account' else []
fields += [parent_fieldname + ' as parent']
acc = frappe.get_list(doctype, fields=fields, filters=filters)

View File

@@ -303,14 +303,17 @@ frappe.ui.form.on('Asset', {
},
set_depreciation_rate: function(frm, row) {
if (row.total_number_of_depreciations && row.frequency_of_depreciation) {
if (row.total_number_of_depreciations && row.frequency_of_depreciation
&& row.expected_value_after_useful_life) {
frappe.call({
method: "get_depreciation_rate",
doc: frm.doc,
args: row,
callback: function(r) {
if (r.message) {
frappe.model.set_value(row.doctype, row.name, "rate_of_depreciation", r.message);
frappe.flags.dont_change_rate = true;
frappe.model.set_value(row.doctype, row.name,
"rate_of_depreciation", flt(r.message, precision("rate_of_depreciation", row)));
}
}
});
@@ -338,6 +341,14 @@ frappe.ui.form.on('Asset Finance Book', {
total_number_of_depreciations: function(frm, cdt, cdn) {
const row = locals[cdt][cdn];
frm.events.set_depreciation_rate(frm, row);
},
rate_of_depreciation: function(frm, cdt, cdn) {
if(!frappe.flags.dont_change_rate) {
frappe.model.set_value(cdt, cdn, "expected_value_after_useful_life", 0);
}
frappe.flags.dont_change_rate = false;
}
});

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext, math, json
from frappe import _
from six import string_types
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, add_days
from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \
@@ -101,97 +101,88 @@ class Asset(AccountsController):
def set_depreciation_rate(self):
for d in self.get("finance_books"):
d.rate_of_depreciation = self.get_depreciation_rate(d, on_validate=True)
d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True),
d.precision("rate_of_depreciation"))
def make_depreciation_schedule(self):
depreciation_method = [d.depreciation_method for d in self.finance_books]
if 'Manual' not in depreciation_method:
if 'Manual' not in [d.depreciation_method for d in self.finance_books]:
self.schedules = []
if not self.get("schedules") and self.available_for_use_date:
total_depreciations = sum([d.total_number_of_depreciations for d in self.get('finance_books')])
if self.get("schedules") or not self.available_for_use_date:
return
for d in self.get('finance_books'):
self.validate_asset_finance_books(d)
for d in self.get('finance_books'):
self.validate_asset_finance_books(d)
value_after_depreciation = (flt(self.gross_purchase_amount) -
flt(self.opening_accumulated_depreciation))
value_after_depreciation = (flt(self.gross_purchase_amount) -
flt(self.opening_accumulated_depreciation))
d.value_after_depreciation = value_after_depreciation
d.value_after_depreciation = value_after_depreciation
no_of_depreciations = cint(d.total_number_of_depreciations - 1) - cint(self.number_of_depreciations_booked)
end_date = add_months(d.depreciation_start_date,
no_of_depreciations * cint(d.frequency_of_depreciation))
number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \
cint(self.number_of_depreciations_booked)
total_days = date_diff(end_date, self.available_for_use_date)
rate_per_day = (value_after_depreciation - d.get("expected_value_after_useful_life")) / total_days
has_pro_rata = self.check_is_pro_rata(d)
number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \
cint(self.number_of_depreciations_booked)
if has_pro_rata:
number_of_pending_depreciations += 1
from_date = self.available_for_use_date
if number_of_pending_depreciations:
next_depr_date = getdate(add_months(self.available_for_use_date,
number_of_pending_depreciations * 12))
if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1
and getdate(d.depreciation_start_date) < next_depr_date):
skip_row = False
for n in range(number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row: continue
number_of_pending_depreciations += 1
for n in range(number_of_pending_depreciations):
if n == list(range(number_of_pending_depreciations))[-1]:
schedule_date = add_months(self.available_for_use_date, n * 12)
previous_scheduled_date = add_months(d.depreciation_start_date, (n-1) * 12)
depreciation_amount = \
self.get_depreciation_amount_prorata_temporis(value_after_depreciation,
d, previous_scheduled_date, schedule_date)
depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
d.total_number_of_depreciations, d)
elif n == list(range(number_of_pending_depreciations))[0]:
schedule_date = d.depreciation_start_date
depreciation_amount = \
self.get_depreciation_amount_prorata_temporis(value_after_depreciation,
d, self.available_for_use_date, schedule_date)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(d.depreciation_start_date,
n * cint(d.frequency_of_depreciation))
else:
schedule_date = add_months(d.depreciation_start_date, n * 12)
depreciation_amount = \
self.get_depreciation_amount_prorata_temporis(value_after_depreciation, d)
# For first row
if has_pro_rata and n==0:
depreciation_amount, days = get_pro_rata_amt(d, depreciation_amount,
self.available_for_use_date, d.depreciation_start_date)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
to_date = add_months(self.available_for_use_date,
n * cint(d.frequency_of_depreciation))
if value_after_depreciation != 0:
value_after_depreciation -= flt(depreciation_amount)
depreciation_amount, days = get_pro_rata_amt(d,
depreciation_amount, schedule_date, to_date)
self.append("schedules", {
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": d.depreciation_method,
"finance_book": d.finance_book,
"finance_book_id": d.idx
})
else:
for n in range(number_of_pending_depreciations):
schedule_date = add_months(d.depreciation_start_date,
n * cint(d.frequency_of_depreciation))
schedule_date = add_days(schedule_date, days)
if d.depreciation_method in ("Straight Line", "Manual"):
days = date_diff(schedule_date, from_date)
if n == 0: days += 1
if not depreciation_amount: continue
value_after_depreciation -= flt(depreciation_amount,
self.precision("gross_purchase_amount"))
depreciation_amount = days * rate_per_day
from_date = schedule_date
else:
depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
d.total_number_of_depreciations, d)
# Adjust depreciation amount in the last period based on the expected value after useful life
if d.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != d.expected_value_after_useful_life)
or value_after_depreciation < d.expected_value_after_useful_life):
depreciation_amount += (value_after_depreciation - d.expected_value_after_useful_life)
skip_row = True
if depreciation_amount:
value_after_depreciation -= flt(depreciation_amount)
if depreciation_amount > 0:
self.append("schedules", {
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": d.depreciation_method,
"finance_book": d.finance_book,
"finance_book_id": d.idx
})
self.append("schedules", {
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": d.depreciation_method,
"finance_book": d.finance_book,
"finance_book_id": d.idx
})
def check_is_pro_rata(self, row):
has_pro_rata = False
days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if days < total_days:
has_pro_rata = True
return has_pro_rata
def validate_asset_finance_books(self, row):
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
@@ -261,31 +252,14 @@ class Asset(AccountsController):
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
if row.depreciation_method in ["Straight Line", "Manual"]:
amt = (flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) -
flt(self.opening_accumulated_depreciation))
depreciation_amount = amt * row.rate_of_depreciation
else:
depreciation_amount = flt(depreciable_value) * (flt(row.rate_of_depreciation) / 100)
value_after_depreciation = flt(depreciable_value) - depreciation_amount
if value_after_depreciation < flt(row.expected_value_after_useful_life):
depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life)
return depreciation_amount
def get_depreciation_amount_prorata_temporis(self, depreciable_value, row, start_date=None, end_date=None):
if start_date and end_date:
prorata_temporis = min(abs(flt(date_diff(str(end_date), str(start_date)))) / flt(frappe.db.get_value("Asset Settings", None, "number_of_days_in_fiscal_year")), 1)
else:
prorata_temporis = 1
precision = self.precision("gross_purchase_amount")
if row.depreciation_method in ("Straight Line", "Manual"):
depreciation_amount = (flt(row.value_after_depreciation) -
flt(row.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) -
cint(self.number_of_depreciations_booked)) * prorata_temporis
cint(self.number_of_depreciations_booked))
else:
depreciation_amount = self.get_depreciation_amount(depreciable_value, row.total_number_of_depreciations, row)
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision)
return depreciation_amount
@@ -301,20 +275,17 @@ class Asset(AccountsController):
flt(accumulated_depreciation_after_full_schedule),
self.precision('gross_purchase_amount'))
if row.expected_value_after_useful_life < asset_value_after_full_schedule:
if (row.expected_value_after_useful_life and
row.expected_value_after_useful_life < asset_value_after_full_schedule):
frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}")
.format(row.idx, asset_value_after_full_schedule))
elif not row.expected_value_after_useful_life:
row.expected_value_after_useful_life = asset_value_after_full_schedule
def validate_cancellation(self):
if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"):
frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status))
if self.purchase_invoice:
frappe.throw(_("Please cancel Purchase Invoice {0} first").format(self.purchase_invoice))
if self.purchase_receipt:
frappe.throw(_("Please cancel Purchase Receipt {0} first").format(self.purchase_receipt))
def delete_depreciation_entries(self):
for d in self.get("schedules"):
if d.journal_entry:
@@ -388,7 +359,8 @@ class Asset(AccountsController):
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"credit": self.purchase_receipt_amount,
"credit_in_account_currency": self.purchase_receipt_amount
"credit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center
}))
gl_entries.append(self.get_gl_dict({
@@ -397,7 +369,8 @@ class Asset(AccountsController):
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"debit": self.purchase_receipt_amount,
"debit_in_account_currency": self.purchase_receipt_amount
"debit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center
}))
if gl_entries:
@@ -410,15 +383,7 @@ class Asset(AccountsController):
if isinstance(args, string_types):
args = json.loads(args)
number_of_depreciations_booked = 0
if self.is_existing_asset:
number_of_depreciations_booked = self.number_of_depreciations_booked
float_precision = cint(frappe.db.get_default("float_precision")) or 2
tot_no_of_depreciation = flt(args.get("total_number_of_depreciations")) - flt(number_of_depreciations_booked)
if args.get("depreciation_method") in ["Straight Line", "Manual"]:
return 1.0 / tot_no_of_depreciation
if args.get("depreciation_method") == 'Double Declining Balance':
return 200.0 / args.get("total_number_of_depreciations")
@@ -598,3 +563,15 @@ def make_journal_entry(asset_name):
def is_cwip_accounting_disabled():
return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting"))
def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
days = date_diff(to_date, from_date)
total_days = get_total_days(to_date, row.frequency_of_depreciation)
return (depreciation_amount * flt(days)) / flt(total_days), days
def get_total_days(date, frequency):
period_start_date = add_months(date,
cint(frequency) * -1)
return date_diff(date, period_start_date)

View File

@@ -88,23 +88,23 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.available_for_use_date = '2020-06-06'
asset.purchase_date = '2020-06-06'
asset.available_for_use_date = '2030-01-01'
asset.purchase_date = '2030-01-01'
asset.append("finance_books", {
"expected_value_after_useful_life": 10000,
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-06-06"
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
asset.save()
self.assertEqual(asset.status, "Draft")
expected_schedules = [
["2020-06-06", 147.54, 147.54],
["2021-04-06", 44852.46, 45000.0],
["2022-02-06", 45000.0, 90000.00]
["2030-12-31", 30000.00, 30000.00],
["2031-12-31", 30000.00, 60000.00],
["2032-12-31", 30000.00, 90000.00]
]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -118,20 +118,21 @@ class TestAsset(unittest.TestCase):
asset.calculate_depreciation = 1
asset.number_of_depreciations_booked = 1
asset.opening_accumulated_depreciation = 40000
asset.available_for_use_date = "2030-06-06"
asset.append("finance_books", {
"expected_value_after_useful_life": 10000,
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-06-06"
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
asset.insert()
self.assertEqual(asset.status, "Draft")
asset.save()
expected_schedules = [
["2020-06-06", 164.47, 40164.47],
["2021-04-06", 49835.53, 90000.00]
["2030-12-31", 14246.58, 54246.58],
["2031-12-31", 25000.00, 79246.58],
["2032-06-06", 10753.42, 90000.00]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in asset.get("schedules")]
@@ -145,24 +146,23 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.available_for_use_date = '2020-06-06'
asset.purchase_date = '2020-06-06'
asset.available_for_use_date = '2030-01-01'
asset.purchase_date = '2030-01-01'
asset.append("finance_books", {
"expected_value_after_useful_life": 10000,
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Double Declining Balance",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-06-06"
"frequency_of_depreciation": 12,
"depreciation_start_date": '2030-12-31'
})
asset.insert()
self.assertEqual(asset.status, "Draft")
asset.save()
expected_schedules = [
["2020-06-06", 66666.67, 66666.67],
["2021-04-06", 22222.22, 88888.89],
["2022-02-06", 1111.11, 90000.0]
['2030-12-31', 66667.00, 66667.00],
['2031-12-31', 22222.11, 88889.11],
['2032-12-31', 1110.89, 90000.0]
]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -177,23 +177,21 @@ class TestAsset(unittest.TestCase):
asset.is_existing_asset = 1
asset.number_of_depreciations_booked = 1
asset.opening_accumulated_depreciation = 50000
asset.available_for_use_date = '2030-01-01'
asset.purchase_date = '2029-11-30'
asset.append("finance_books", {
"expected_value_after_useful_life": 10000,
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Double Declining Balance",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-06-06"
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
asset.insert()
self.assertEqual(asset.status, "Draft")
asset.save()
asset.save()
expected_schedules = [
["2020-06-06", 33333.33, 83333.33],
["2021-04-06", 6666.67, 90000.0]
["2030-12-31", 33333.50, 83333.50],
["2031-12-31", 6666.50, 90000.0]
]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@@ -209,25 +207,25 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.purchase_date = '2020-01-30'
asset.purchase_date = '2030-01-30'
asset.is_existing_asset = 0
asset.available_for_use_date = "2020-01-30"
asset.available_for_use_date = "2030-01-30"
asset.append("finance_books", {
"expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-12-31"
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
asset.insert()
asset.save()
expected_schedules = [
["2020-12-31", 28000.0, 28000.0],
["2021-12-31", 30000.0, 58000.0],
["2022-12-31", 30000.0, 88000.0],
["2023-01-30", 2000.0, 90000.0]
["2030-12-31", 27534.25, 27534.25],
["2031-12-31", 30000.0, 57534.25],
["2032-12-31", 30000.0, 87534.25],
["2033-01-30", 2465.75, 90000.0]
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@@ -266,8 +264,8 @@ class TestAsset(unittest.TestCase):
self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR")
expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 32129.24),
("_Test Depreciations - _TC", 32129.24, 0.0)
("_Test Accumulated Depreciations - _TC", 0.0, 30000.0),
("_Test Depreciations - _TC", 30000.0, 0.0)
)
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
@@ -277,15 +275,15 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle)
self.assertEqual(asset.get("value_after_depreciation"), 0)
def test_depreciation_entry_for_wdv(self):
def test_depreciation_entry_for_wdv_without_pro_rata(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=8000.0, location="Test Location")
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.available_for_use_date = '2030-06-06'
asset.purchase_date = '2030-06-06'
asset.available_for_use_date = '2030-01-01'
asset.purchase_date = '2030-01-01'
asset.append("finance_books", {
"expected_value_after_useful_life": 1000,
"depreciation_method": "Written Down Value",
@@ -298,9 +296,41 @@ class TestAsset(unittest.TestCase):
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
["2030-12-31", 4000.0, 4000.0],
["2031-12-31", 2000.0, 6000.0],
["2032-12-31", 1000.0, 7000.0],
["2030-12-31", 4000.00, 4000.00],
["2031-12-31", 2000.00, 6000.00],
["2032-12-31", 1000.00, 7000.0],
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
for d in asset.get("schedules")]
self.assertEqual(schedules, expected_schedules)
def test_pro_rata_depreciation_entry_for_wdv(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=8000.0, location="Test Location")
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.available_for_use_date = '2030-06-06'
asset.purchase_date = '2030-01-01'
asset.append("finance_books", {
"expected_value_after_useful_life": 1000,
"depreciation_method": "Written Down Value",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
asset.save(ignore_permissions=True)
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
["2030-12-31", 2279.45, 2279.45],
["2031-12-31", 2860.28, 5139.73],
["2032-12-31", 1430.14, 6569.87],
["2033-06-06", 430.13, 7000.0],
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@@ -346,18 +376,19 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.available_for_use_date = '2020-06-06'
asset.purchase_date = '2020-06-06'
asset.available_for_use_date = nowdate()
asset.purchase_date = nowdate()
asset.append("finance_books", {
"expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-06-06"
"depreciation_start_date": nowdate()
})
asset.insert()
asset.submit()
post_depreciation_entries(date="2021-01-01")
post_depreciation_entries(date=add_months(nowdate(), 10))
scrap_asset(asset.name)
@@ -366,9 +397,9 @@ class TestAsset(unittest.TestCase):
self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = (
("_Test Accumulated Depreciations - _TC", 147.54, 0.0),
("_Test Accumulated Depreciations - _TC", 30000.0, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 99852.46, 0.0)
("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0)
)
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
@@ -412,9 +443,9 @@ class TestAsset(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
expected_gle = (
("_Test Accumulated Depreciations - _TC", 23051.47, 0.0),
("_Test Accumulated Depreciations - _TC", 20392.16, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 51948.53, 0.0),
("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0),
("Debtors - _TC", 25000.0, 0.0)
)

View File

@@ -56,8 +56,7 @@ def assign_tasks(asset_maintenance_name, assign_to_member, maintenance_task, nex
def calculate_next_due_date(periodicity, start_date = None, end_date = None, last_completion_date = None, next_due_date = None):
if not start_date and not last_completion_date:
start_date = frappe.utils.now()
if last_completion_date and (last_completion_date > start_date or not start_date):
if last_completion_date and ((start_date and last_completion_date > start_date) or not start_date):
start_date = last_completion_date
if periodicity == 'Daily':
next_due_date = add_days(start_date, 1)
@@ -115,4 +114,4 @@ def get_maintenance_log(asset_name):
select maintenance_status, count(asset_name) as count, asset_name
from `tabAsset Maintenance Log`
where asset_name=%s group by maintenance_status""",
(asset_name), as_dict=1)
(asset_name), as_dict=1)

View File

@@ -46,75 +46,6 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "schedule_based_on_fiscal_year",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Calculate Prorated Depreciation Schedule Based on Fiscal Year",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "360",
"depends_on": "eval:doc.schedule_based_on_fiscal_year",
"description": "This value is used for pro-rata temporis calculation",
"fetch_if_empty": 0,
"fieldname": "number_of_days_in_fiscal_year",
"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": "Number of Days in Fiscal Year",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@@ -159,7 +90,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2019-03-08 10:44:41.924547",
"modified": "2019-05-26 18:31:19.930563",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Settings",

View File

@@ -10,7 +10,8 @@ frappe.ui.form.on("Purchase Order", {
frm.custom_make_buttons = {
'Purchase Receipt': 'Receipt',
'Purchase Invoice': 'Invoice',
'Stock Entry': 'Material to Supplier'
'Stock Entry': 'Material to Supplier',
'Payment Entry': 'Payment'
}
frm.set_query("reserve_warehouse", "supplied_items", function() {

View File

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

View File

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

View File

@@ -58,7 +58,9 @@ class AccountsController(TransactionBase):
def validate(self):
self.validate_qty_is_not_zero()
if not self.get('is_return'):
self.validate_qty_is_not_zero()
if self.get("_action") and self._action != "update_after_submit":
self.set_missing_values(for_validate=True)
@@ -85,7 +87,7 @@ class AccountsController(TransactionBase):
self.validate_currency()
if self.doctype == 'Purchase Invoice':
self.validate_paid_amount()
self.calculate_paid_amount()
if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
@@ -129,22 +131,23 @@ class AccountsController(TransactionBase):
else:
df.set("print_hide", 1)
def validate_paid_amount(self):
def calculate_paid_amount(self):
if hasattr(self, "is_pos") or hasattr(self, "is_paid"):
is_paid = self.get("is_pos") or self.get("is_paid")
if cint(is_paid) == 1:
if flt(self.paid_amount) == 0 and flt(self.outstanding_amount) > 0:
if self.cash_bank_account:
self.paid_amount = flt(flt(self.outstanding_amount), self.precision("paid_amount"))
self.base_paid_amount = flt(self.paid_amount * self.conversion_rate,
self.precision("base_paid_amount"))
else:
# show message that the amount is not paid
self.paid_amount = 0
frappe.throw(
_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified"))
else:
frappe.db.set(self, 'paid_amount', 0)
if is_paid:
if not self.cash_bank_account:
# show message that the amount is not paid
frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified"))
if cint(self.is_return) and (self.grand_total > self.paid_amount):
self.paid_amount = flt(flt(self.grand_total), self.precision("paid_amount"))
elif not flt(self.paid_amount) and flt(self.outstanding_amount) > 0:
self.paid_amount = flt(flt(self.outstanding_amount), self.precision("paid_amount"))
self.base_paid_amount = flt(self.paid_amount * self.conversion_rate,
self.precision("base_paid_amount"))
def set_missing_values(self, for_validate=False):
if frappe.flags.in_test:
@@ -825,7 +828,7 @@ class AccountsController(TransactionBase):
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
grand_total = grand_total - flt(self.write_off_amount)
if total != grand_total:
if total != flt(grand_total, self.precision("grand_total")):
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
def is_rounded_total_disabled(self):
@@ -1110,6 +1113,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name):
.format(child_item.idx, child_item.item_code))
else:
child_item.rate = flt(d.get("rate"))
if flt(child_item.price_list_rate):
child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0, \
child_item.precision("discount_percentage"))
child_item.flags.ignore_validate_update_after_submit = True
child_item.save()

View File

@@ -337,7 +337,7 @@ class BuyingController(StockController):
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
rm.consumed_qty = required_qty
rm.description = bom_item.description
if item.batch_no and not rm.batch_no:
if item.batch_no and frappe.db.get_value("Item", rm.rm_item_code, "has_batch_no") and not rm.batch_no:
rm.batch_no = item.batch_no
# get raw materials rate
@@ -395,7 +395,9 @@ class BuyingController(StockController):
def set_qty_as_per_stock_uom(self):
for d in self.get("items"):
if d.meta.get_field("stock_qty"):
if not d.conversion_factor:
# Check if item code is present
# Conversion factor should not be mandatory for non itemized items
if not d.conversion_factor and d.item_code:
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
@@ -715,7 +717,7 @@ def get_items_from_bom(item_code, bom, exploded_item=1):
where
t2.parent = t1.name and t1.item = %s
and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
and t2.item_code = t3.name and t3.is_stock_item = 1""".format(doctype),
and t2.item_code = t3.name""".format(doctype),
(item_code, bom), as_dict=1)
if not bom_items:

View File

@@ -292,6 +292,7 @@ def copy_attributes_to_variant(item, variant):
if not variant.description:
variant.description = ""
else:
if item.variant_based_on=='Item Attribute':
if variant.attributes:
attributes_description = item.description + " "
@@ -299,7 +300,7 @@ def copy_attributes_to_variant(item, variant):
attributes_description += "<div>" + d.attribute + ": " + cstr(d.attribute_value) + "</div>"
if attributes_description not in variant.description:
variant.description += attributes_description
variant.description = attributes_description
def make_variant_item_code(template_item_code, template_item_name, variant):
"""Uses template's item code and abbreviations to make variant's item code"""

View File

@@ -206,11 +206,11 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
idx desc, name
limit %(start)s, %(page_len)s """.format(
fcond=get_filters_cond(doctype, filters, conditions),
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
mcond=get_match_cond(doctype),
key=frappe.db.escape(searchfield)),
{
'txt': "%%%s%%" % frappe.db.escape(txt),
'txt': "%"+frappe.db.escape(txt)+"%",
'_txt': txt.replace("%", ""),
'start': start or 0,
'page_len': page_len or 20
@@ -371,7 +371,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select tabAccount.name from `tabAccount`
where (tabAccount.report_type = "Profit and Loss"
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed"))
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
and tabAccount.is_group=0
and tabAccount.docstatus!=2
and tabAccount.{key} LIKE %(txt)s

View File

@@ -18,34 +18,31 @@ def validate_return(doc):
validate_returned_items(doc)
def validate_return_against(doc):
filters = {"doctype": doc.doctype, "docstatus": 1, "company": doc.company}
if doc.meta.get_field("customer") and doc.customer:
filters["customer"] = doc.customer
elif doc.meta.get_field("supplier") and doc.supplier:
filters["supplier"] = doc.supplier
if not frappe.db.exists(filters):
if not frappe.db.exists(doc.doctype, doc.return_against):
frappe.throw(_("Invalid {0}: {1}")
.format(doc.meta.get_label("return_against"), doc.return_against))
else:
ref_doc = frappe.get_doc(doc.doctype, doc.return_against)
# validate posting date time
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier"
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
if ref_doc.company == doc.company and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.docstatus == 1:
# validate posting date time
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
# validate same exchange rate
if doc.conversion_rate != ref_doc.conversion_rate:
frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
.format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
# validate update stock
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
.format(doc.return_against))
# validate same exchange rate
if doc.conversion_rate != ref_doc.conversion_rate:
frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
.format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
# validate update stock
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
.format(doc.return_against))
def validate_returned_items(doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -75,7 +72,7 @@ def validate_returned_items(doc):
items_returned = False
for d in doc.get("items"):
if flt(d.qty) < 0 or d.get('received_qty') < 0:
if d.item_code and (flt(d.qty) < 0 or d.get('received_qty') < 0):
if d.item_code not in valid_items:
frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}")
.format(d.idx, d.item_code, doc.doctype, doc.return_against))
@@ -107,6 +104,9 @@ def validate_returned_items(doc):
items_returned = True
elif d.item_name:
items_returned = True
if not items_returned:
frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
@@ -246,6 +246,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
elif doc.doctype == 'Purchase Invoice':
doc.paid_amount = -1 * source.paid_amount
doc.base_paid_amount = -1 * source.base_paid_amount
doc.payment_terms_template = ''
doc.payment_schedule = []
if doc.get("is_return") and hasattr(doc, "packed_items"):
for d in doc.get("packed_items"):

View File

@@ -45,6 +45,7 @@ class SellingController(StockController):
self.set_gross_profit()
set_default_income_account_for_item(self)
self.set_customer_address()
self.validate_for_duplicate_items()
def set_missing_values(self, for_validate=False):
@@ -381,6 +382,34 @@ class SellingController(StockController):
if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field)))
def validate_for_duplicate_items(self):
check_list, chk_dupl_itm = [], []
if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
return
for d in self.get('items'):
if self.doctype == "Sales Invoice":
e = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
f = [d.item_code, d.description, d.sales_order or d.delivery_note]
elif self.doctype == "Delivery Note":
e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
elif self.doctype in ["Sales Order", "Quotation"]:
e = [d.item_code, d.description, d.warehouse, '']
f = [d.item_code, d.description]
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
if e in check_list:
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
check_list.append(e)
else:
if f in chk_dupl_itm:
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
chk_dupl_itm.append(f)
def validate_items(self):
# validate items to see if they have is_sales_item enabled
from erpnext.controllers.buying_controller import validate_item_type

View File

@@ -38,7 +38,6 @@ status_map = {
["To Bill", "eval:self.per_delivered == 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"],
["Completed", "eval:self.order_type == 'Maintenance' and self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
],
@@ -94,7 +93,8 @@ status_map = {
["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"]
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
],
"Bank Transaction": [
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],

View File

@@ -15,6 +15,9 @@ class calculate_taxes_and_totals(object):
self.calculate()
def calculate(self):
if not len(self.doc.get("items")):
return
self.discount_amount_applied = False
self._calculate()
@@ -73,7 +76,12 @@ class calculate_taxes_and_totals(object):
item.discount_amount = item.price_list_rate - item.rate
item.net_rate = item.rate
item.amount = flt(item.rate * item.qty, item.precision("amount"))
if not item.qty and self.doc.get("is_return"):
item.amount = flt(-1 * item.rate, item.precision("amount"))
else:
item.amount = flt(item.rate * item.qty, item.precision("amount"))
item.net_amount = item.amount
self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])
@@ -326,7 +334,7 @@ class calculate_taxes_and_totals(object):
self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \
if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
else self.doc.base_net_total

View File

@@ -39,7 +39,6 @@ def validate_filters(filters):
frappe.throw(_("'Based On' and 'Group By' can not be same"))
def get_data(filters, conditions):
data = []
inc, cond= '',''
query_details = conditions["based_on_select"] + conditions["period_wise_select"]
@@ -47,13 +46,17 @@ def get_data(filters, conditions):
posting_date = 't1.transaction_date'
if conditions.get('trans') in ['Sales Invoice', 'Purchase Invoice', 'Purchase Receipt', 'Delivery Note']:
posting_date = 't1.posting_date'
if filters.period_based_on:
posting_date = 't1.'+filters.period_based_on
if conditions["based_on_select"] in ["t1.project,", "t2.project,"]:
cond = ' and '+ conditions["based_on_select"][:-1] +' IS Not NULL'
if conditions.get('trans') in ['Sales Order', 'Purchase Order']:
cond += " and t1.status != 'Closed'"
if conditions.get('trans') == 'Quotation' and filters.get("group_by") == 'Customer':
cond += " and t1.quotation_to = 'Customer'"
year_start_date, year_end_date = frappe.db.get_value("Fiscal Year",
filters.get('fiscal_year'), ["year_start_date", "year_end_date"])
@@ -64,7 +67,7 @@ def get_data(filters, conditions):
if filters.get("group_by") == 'Item':
sel_col = 't2.item_code'
elif filters.get("group_by") == 'Customer':
sel_col = 't1.customer'
sel_col = 't1.party_name' if conditions.get('trans') == 'Quotation' else 't1.customer'
elif filters.get("group_by") == 'Supplier':
sel_col = 't1.supplier'
@@ -225,7 +228,7 @@ def based_wise_columns_query(based_on, trans):
elif based_on == "Customer":
based_on_details["based_on_cols"] = ["Customer:Link/Customer:120", "Territory:Link/Territory:120"]
based_on_details["based_on_select"] = "t1.customer_name, t1.territory, "
based_on_details["based_on_group_by"] = 't1.customer'
based_on_details["based_on_group_by"] = 't1.party_name' if trans == 'Quotation' else 't1.customer'
based_on_details["addl_tables"] = ''
elif based_on == "Customer Group":

View File

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

View File

@@ -88,7 +88,7 @@ def get_status(start_date, end_date):
end_date = getdate(end_date)
now_date = getdate(nowdate())
return "Active" if start_date < now_date < end_date else "Inactive"
return "Active" if start_date <= now_date <= end_date else "Inactive"
def update_status_for_contracts():

View File

@@ -8,14 +8,8 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({
setup: function () {
this.frm.make_methods = {
'Quotation': () => erpnext.utils.create_new_doc('Quotation', {
'quotation_to': this.frm.doc.doctype,
'party_name': this.frm.doc.name
}),
'Opportunity': () => erpnext.utils.create_new_doc('Opportunity', {
'opportunity_from': this.frm.doc.doctype,
'party_name': this.frm.doc.name
})
'Quotation': this.make_quotation,
'Opportunity': this.create_opportunity
}
this.frm.fields_dict.customer.get_query = function (doc, cdt, cdn) {

View File

@@ -18,6 +18,10 @@ frappe.ui.form.on("Opportunity", {
}
}
});
if (frm.doc.opportunity_from && frm.doc.party_name){
frm.trigger('set_contact_link');
}
},
onload_post_render: function(frm) {

View File

@@ -1460,7 +1460,7 @@
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-info-sign",
"icon": "fa fa-info-circle",
"idx": 195,
"image_view": 0,
"in_create": 0,

View File

@@ -7,8 +7,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"letter_head": "",
"modified": "2017-04-17 00:20:27.248275",
"modified": "2019-04-17 00:20:27.248275",
"modified_by": "Administrator",
"module": "CRM",
"name": "Campaign Efficiency",

View File

@@ -6,8 +6,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"letter_head": "",
"modified": "2018-09-17 14:40:52.035394",
"modified": "2019-09-19 14:40:52.035394",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead Conversion Time",

View File

@@ -7,8 +7,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"letter_head": "Shishuvan Secondary School",
"modified": "2018-02-08 15:11:35.339434",
"modified": "2019-02-08 15:11:35.339434",
"modified_by": "Administrator",
"module": "Education",
"name": "Final Assessment Grades",

View File

@@ -62,7 +62,8 @@ def _order(*args, **kwargs):
item_woo_com_id = item.get("product_id")
if frappe.get_value("Item",{"woocommerce_id": item_woo_com_id}):
if frappe.get_value("Item",{"woocommerce_id": item_woo_com_id}) or\
frappe.get_value("Item",{"item_name": item.get('name')}):
#Edit
link_item(item,1)
else:

View File

@@ -20,6 +20,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 1,
@@ -54,6 +55,7 @@
"collapsible": 1,
"collapsible_depends_on": "eval:doc.client_id && doc.client_secret && doc.redirect_url",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "application_settings",
"fieldtype": "Section Break",
"hidden": 0,
@@ -87,6 +89,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_if_empty": 0,
"fieldname": "client_id",
"fieldtype": "Data",
"hidden": 0,
@@ -120,6 +123,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_if_empty": 0,
"fieldname": "redirect_url",
"fieldtype": "Data",
"hidden": 0,
@@ -153,6 +157,7 @@
"collapsible": 0,
"columns": 0,
"default": "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
"fetch_if_empty": 0,
"fieldname": "token_endpoint",
"fieldtype": "Data",
"hidden": 0,
@@ -185,6 +190,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "application_column_break",
"fieldtype": "Column Break",
"hidden": 0,
@@ -217,6 +223,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_if_empty": 0,
"fieldname": "client_secret",
"fieldtype": "Data",
"hidden": 0,
@@ -250,6 +257,7 @@
"collapsible": 0,
"columns": 0,
"default": "com.intuit.quickbooks.accounting",
"fetch_if_empty": 0,
"fieldname": "scope",
"fieldtype": "Data",
"hidden": 0,
@@ -284,6 +292,7 @@
"collapsible": 0,
"columns": 0,
"default": "https://quickbooks.api.intuit.com/v3",
"fetch_if_empty": 0,
"fieldname": "api_endpoint",
"fieldtype": "Data",
"hidden": 0,
@@ -316,6 +325,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "authorization_settings",
"fieldtype": "Section Break",
"hidden": 0,
@@ -349,6 +359,7 @@
"collapsible": 0,
"columns": 0,
"default": "https://appcenter.intuit.com/connect/oauth2",
"fetch_if_empty": 0,
"fieldname": "authorization_endpoint",
"fieldtype": "Data",
"hidden": 0,
@@ -381,6 +392,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "refresh_token",
"fieldtype": "Small Text",
"hidden": 1,
@@ -413,6 +425,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "code",
"fieldtype": "Data",
"hidden": 1,
@@ -445,6 +458,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "authorization_column_break",
"fieldtype": "Column Break",
"hidden": 0,
@@ -476,6 +490,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "authorization_url",
"fieldtype": "Data",
"hidden": 0,
@@ -508,6 +523,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "access_token",
"fieldtype": "Small Text",
"hidden": 1,
@@ -540,6 +556,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "quickbooks_company_id",
"fieldtype": "Data",
"hidden": 1,
@@ -572,6 +589,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company_settings",
"fieldtype": "Section Break",
"hidden": 1,
@@ -604,6 +622,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -637,6 +656,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_shipping_account",
"fieldtype": "Link",
"hidden": 1,
@@ -670,6 +690,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_warehouse",
"fieldtype": "Link",
"hidden": 1,
@@ -703,6 +724,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company_column_break",
"fieldtype": "Column Break",
"hidden": 0,
@@ -734,6 +756,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default_cost_center",
"fieldtype": "Link",
"hidden": 1,
@@ -767,6 +790,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "undeposited_funds_account",
"fieldtype": "Link",
"hidden": 1,
@@ -804,7 +828,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-17 03:12:53.506229",
"modified": "2019-08-07 05:53:00.920316",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "QuickBooks Migrator",
@@ -834,7 +858,7 @@
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 1,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,

View File

@@ -7,7 +7,9 @@ import frappe
from frappe import _
from frappe.model.document import Document
from requests_oauthlib import OAuth2Session
import json, requests
import json
import requests
import traceback
from erpnext import encode_company_abbr
# QuickBooks requires a redirect URL, User will be redirect to this URL
@@ -32,7 +34,6 @@ def callback(*args, **kwargs):
class QuickBooksMigrator(Document):
def __init__(self, *args, **kwargs):
super(QuickBooksMigrator, self).__init__(*args, **kwargs)
from pprint import pprint
self.oauth = OAuth2Session(
client_id=self.client_id,
redirect_uri=self.redirect_url,
@@ -46,7 +47,9 @@ class QuickBooksMigrator(Document):
if self.company:
# We need a Cost Center corresponding to the selected erpnext Company
self.default_cost_center = frappe.db.get_value('Company', self.company, 'cost_center')
self.default_warehouse = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0})[0]["name"]
company_warehouses = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0})
if company_warehouses:
self.default_warehouse = company_warehouses[0].name
if self.authorization_endpoint:
self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
@@ -218,7 +221,7 @@ class QuickBooksMigrator(Document):
def _fetch_general_ledger(self):
try:
query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint ,self.quickbooks_company_id)
query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint, self.quickbooks_company_id)
response = self._get(query_uri,
params={
"columns": ",".join(["tx_date", "txn_type", "credit_amt", "debt_amt"]),
@@ -493,17 +496,17 @@ class QuickBooksMigrator(Document):
"account_currency": customer["CurrencyRef"]["value"],
"company": self.company,
})[0]["name"]
except Exception as e:
except Exception:
receivable_account = None
erpcustomer = frappe.get_doc({
"doctype": "Customer",
"quickbooks_id": customer["Id"],
"customer_name" : encode_company_abbr(customer["DisplayName"], self.company),
"customer_type" : "Individual",
"customer_group" : "Commercial",
"customer_name": encode_company_abbr(customer["DisplayName"], self.company),
"customer_type": "Individual",
"customer_group": "Commercial",
"default_currency": customer["CurrencyRef"]["value"],
"accounts": [{"company": self.company, "account": receivable_account}],
"territory" : "All Territories",
"territory": "All Territories",
"company": self.company,
}).insert()
if "BillAddr" in customer:
@@ -521,7 +524,7 @@ class QuickBooksMigrator(Document):
item_dict = {
"doctype": "Item",
"quickbooks_id": item["Id"],
"item_code" : encode_company_abbr(item["Name"], self.company),
"item_code": encode_company_abbr(item["Name"], self.company),
"stock_uom": "Unit",
"is_stock_item": 0,
"item_group": "All Item Groups",
@@ -549,14 +552,14 @@ class QuickBooksMigrator(Document):
erpsupplier = frappe.get_doc({
"doctype": "Supplier",
"quickbooks_id": vendor["Id"],
"supplier_name" : encode_company_abbr(vendor["DisplayName"], self.company),
"supplier_group" : "All Supplier Groups",
"supplier_name": encode_company_abbr(vendor["DisplayName"], self.company),
"supplier_group": "All Supplier Groups",
"company": self.company,
}).insert()
if "BillAddr" in vendor:
self._create_address(erpsupplier, "Supplier", vendor["BillAddr"], "Billing")
if "ShipAddr" in vendor:
self._create_address(erpsupplier, "Supplier",vendor["ShipAddr"], "Shipping")
self._create_address(erpsupplier, "Supplier", vendor["ShipAddr"], "Shipping")
except Exception as e:
self._log_error(e)
@@ -829,7 +832,7 @@ class QuickBooksMigrator(Document):
"currency": invoice["CurrencyRef"]["value"],
"conversion_rate": invoice.get("ExchangeRate", 1),
"posting_date": invoice["TxnDate"],
"due_date": invoice.get("DueDate", invoice["TxnDate"]),
"due_date": invoice.get("DueDate", invoice["TxnDate"]),
"credit_to": credit_to_account,
"supplier": frappe.get_all("Supplier",
filters={
@@ -1200,7 +1203,7 @@ class QuickBooksMigrator(Document):
def _create_address(self, entity, doctype, address, address_type):
try :
try:
if not frappe.db.exists({"doctype": "Address", "quickbooks_id": address["Id"]}):
frappe.get_doc({
"doctype": "Address",
@@ -1252,8 +1255,6 @@ class QuickBooksMigrator(Document):
def _log_error(self, execption, data=""):
import json, traceback
traceback.print_exc()
frappe.log_error(title="QuickBooks Migration Error",
message="\n".join([
"Data",

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint, cstr, getdate
from frappe.utils import cint, cstr, getdate, flt
import dateutil
from frappe.model.naming import set_name_by_naming_series
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account,send_registration_sms
@@ -64,7 +64,7 @@ class Patient(Document):
def invoice_patient_registration(self):
frappe.db.set_value("Patient", self.name, "disabled", 0)
send_registration_sms(self)
if(frappe.get_value("Healthcare Settings", None, "registration_fee")>0):
if(flt(frappe.get_value("Healthcare Settings", None, "registration_fee"))>0):
company = frappe.defaults.get_user_default('company')
if not company:
company = frappe.db.get_value("Global Defaults", None, "default_company")

View File

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

View File

@@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe, math
from frappe import _
from frappe.utils import flt, rounded
from frappe.model.mapper import get_mapped_doc
from frappe.model.document import Document
from erpnext.hr.doctype.employee_loan.employee_loan import get_monthly_repayment_amount, check_repayment_method
class EmployeeLoanApplication(Document):
def validate(self):
check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
self.validate_loan_amount()
self.get_repayment_details()
def validate_loan_amount(self):
maximum_loan_limit = frappe.db.get_value('Loan Type', self.loan_type, 'maximum_loan_amount')
if maximum_loan_limit and self.loan_amount > maximum_loan_limit:
frappe.throw(_("Loan Amount cannot exceed Maximum Loan Amount of {0}").format(maximum_loan_limit))
def get_repayment_details(self):
if self.repayment_method == "Repay Over Number of Periods":
self.repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
if self.repayment_method == "Repay Fixed Amount per Period":
monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
if monthly_interest_rate:
monthly_interest_amount = self.loan_amount * monthly_interest_rate
if monthly_interest_amount >= self.repayment_amount:
frappe.throw(_("Repayment amount {} should be greater than monthly interest amount {}").
format(self.repayment_amount, monthly_interest_amount))
self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
math.log(self.repayment_amount - (monthly_interest_amount))) /
(math.log(1 + monthly_interest_rate)))
else:
self.repayment_periods = self.loan_amount / self.repayment_amount
self.calculate_payable_amount()
def calculate_payable_amount(self):
balance_amount = self.loan_amount
self.total_payable_amount = 0
self.total_payable_interest = 0
while(balance_amount > 0):
interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100))
balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount)
self.total_payable_interest += interest_amount
self.total_payable_amount = self.loan_amount + self.total_payable_interest
@frappe.whitelist()
def make_employee_loan(source_name, target_doc = None):
doclist = get_mapped_doc("Employee Loan Application", source_name, {
"Employee Loan Application": {
"doctype": "Employee Loan",
"validation": {
"docstatus": ["=", 1]
}
}
}, target_doc)
return doclist

View File

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

View File

@@ -39,31 +39,19 @@ frappe.ui.form.on('Loan', {
},
refresh: function (frm) {
if (frm.doc.docstatus == 1 && frm.doc.status == "Sanctioned") {
frm.add_custom_button(__('Make Disbursement Entry'), function() {
frm.trigger("make_jv");
})
}
if (frm.doc.repayment_schedule) {
let total_amount_paid = 0;
$.each(frm.doc.repayment_schedule || [], function(i, row) {
if (row.paid) {
total_amount_paid += row.total_payment;
}
});
frm.set_value("total_amount_paid", total_amount_paid);
; }
if (frm.doc.docstatus == 1 && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) {
frm.add_custom_button(__('Make Repayment Entry'), function() {
frm.trigger("make_repayment_entry");
})
if (frm.doc.docstatus == 1) {
if (frm.doc.status == "Sanctioned") {
frm.add_custom_button(__('Create Disbursement Entry'), function() {
frm.trigger("make_jv");
}).addClass("btn-primary");
} else if (frm.doc.status == "Disbursed" && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) {
frm.add_custom_button(__('Create Repayment Entry'), function() {
frm.trigger("make_repayment_entry");
}).addClass("btn-primary");
}
}
frm.trigger("toggle_fields");
},
status: function (frm) {
frm.toggle_reqd("disbursement_date", frm.doc.status == 'Disbursed')
frm.toggle_reqd("repayment_start_date", frm.doc.status == 'Disbursed')
},
make_jv: function (frm) {
frappe.call({

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
@@ -20,6 +21,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "applicant_type",
"fieldtype": "Select",
"hidden": 0,
@@ -53,6 +55,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "applicant",
"fieldtype": "Dynamic Link",
"hidden": 0,
@@ -86,6 +89,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "applicant_name",
"fieldtype": "Data",
"hidden": 0,
@@ -118,6 +122,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "loan_application",
"fieldtype": "Link",
"hidden": 0,
@@ -151,6 +156,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "loan_type",
"fieldtype": "Link",
"hidden": 0,
@@ -184,6 +190,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
@@ -215,7 +222,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"default": "Today",
"fetch_if_empty": 0,
"fieldname": "posting_date",
"fieldtype": "Date",
"hidden": 0,
@@ -248,6 +256,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -282,6 +291,7 @@
"collapsible": 0,
"columns": 0,
"default": "Sanctioned",
"fetch_if_empty": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
@@ -299,7 +309,7 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -316,6 +326,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.applicant_type==\"Employee\"",
"fetch_if_empty": 0,
"fieldname": "repay_from_salary",
"fieldtype": "Check",
"hidden": 0,
@@ -348,6 +359,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
@@ -380,6 +392,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "loan_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -415,6 +428,7 @@
"columns": 0,
"default": "",
"fetch_from": "loan_type.rate_of_interest",
"fetch_if_empty": 0,
"fieldname": "rate_of_interest",
"fieldtype": "Percent",
"hidden": 0,
@@ -448,6 +462,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.status==\"Disbursed\"",
"fetch_if_empty": 0,
"fieldname": "disbursement_date",
"fieldtype": "Date",
"hidden": 0,
@@ -480,6 +496,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "repayment_start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -499,7 +516,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@@ -512,6 +529,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_11",
"fieldtype": "Column Break",
"hidden": 0,
@@ -544,6 +562,7 @@
"collapsible": 0,
"columns": 0,
"default": "Repay Over Number of Periods",
"fetch_if_empty": 0,
"fieldname": "repayment_method",
"fieldtype": "Select",
"hidden": 0,
@@ -579,6 +598,7 @@
"columns": 0,
"default": "",
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "repayment_periods",
"fieldtype": "Int",
"hidden": 0,
@@ -613,6 +633,7 @@
"columns": 0,
"default": "",
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "monthly_repayment_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -646,6 +667,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "account_info",
"fieldtype": "Section Break",
"hidden": 0,
@@ -678,6 +700,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "mode_of_payment",
"fieldtype": "Link",
"hidden": 0,
@@ -711,6 +734,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payment_account",
"fieldtype": "Link",
"hidden": 0,
@@ -744,6 +768,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
@@ -775,6 +800,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "loan_account",
"fieldtype": "Link",
"hidden": 0,
@@ -808,6 +834,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "interest_income_account",
"fieldtype": "Link",
"hidden": 0,
@@ -841,6 +868,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_15",
"fieldtype": "Section Break",
"hidden": 0,
@@ -873,6 +901,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "repayment_schedule",
"fieldtype": "Table",
"hidden": 0,
@@ -906,6 +935,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_17",
"fieldtype": "Section Break",
"hidden": 0,
@@ -939,6 +969,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
"fetch_if_empty": 0,
"fieldname": "total_payment",
"fieldtype": "Currency",
"hidden": 0,
@@ -972,6 +1003,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_19",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1004,6 +1036,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
"fetch_if_empty": 0,
"fieldname": "total_interest_payable",
"fieldtype": "Currency",
"hidden": 0,
@@ -1037,6 +1070,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_amount_paid",
"fieldtype": "Currency",
"hidden": 0,
@@ -1070,6 +1104,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -1106,7 +1141,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:53.267145",
"modified": "2019-07-10 13:04:20.953694",
"modified_by": "Administrator",
"module": "HR",
"name": "Loan",
@@ -1149,7 +1184,6 @@
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"user_permission_doctypes": "[\"Employee\"]",
"write": 0
}
],

View File

@@ -6,29 +6,33 @@ from __future__ import unicode_literals
import frappe, math, json
import erpnext
from frappe import _
from frappe.utils import flt, rounded, add_months, nowdate
from frappe.utils import flt, rounded, add_months, nowdate, getdate
from erpnext.controllers.accounts_controller import AccountsController
class Loan(AccountsController):
def validate(self):
check_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods)
validate_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods)
self.set_missing_fields()
self.make_repayment_schedule()
self.set_repayment_period()
self.calculate_totals()
def set_missing_fields(self):
if not self.company:
self.company = erpnext.get_default_company()
if not self.posting_date:
self.posting_date = nowdate()
if self.loan_type and not self.rate_of_interest:
self.rate_of_interest = frappe.db.get_value("Loan Type", self.loan_type, "rate_of_interest")
if self.repayment_method == "Repay Over Number of Periods":
self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
if self.status == "Repaid/Closed":
self.total_amount_paid = self.total_payment
if self.status == 'Disbursed' and self.repayment_start_date < self.disbursement_date:
frappe.throw(_("Repayment Start Date cannot be before Disbursement Date."))
if self.status == "Disbursed":
self.make_repayment_schedule()
self.set_repayment_period()
self.calculate_totals()
def make_jv_entry(self):
self.check_permission('write')
@@ -105,20 +109,31 @@ def update_total_amount_paid(doc):
frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid)
def update_disbursement_status(doc):
disbursement = frappe.db.sql("""select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount
from `tabGL Entry` where account = %s and against_voucher_type = 'Loan' and against_voucher = %s""",
(doc.payment_account, doc.name), as_dict=1)[0]
if disbursement.disbursed_amount == doc.loan_amount:
frappe.db.set_value("Loan", doc.name , "status", "Disbursed")
if disbursement.disbursed_amount == 0:
frappe.db.set_value("Loan", doc.name , "status", "Sanctioned")
if disbursement.disbursed_amount > doc.loan_amount:
frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount))
if disbursement.disbursed_amount > 0:
frappe.db.set_value("Loan", doc.name , "disbursement_date", disbursement.posting_date)
frappe.db.set_value("Loan", doc.name , "repayment_start_date", disbursement.posting_date)
disbursement = frappe.db.sql("""
select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount
from `tabGL Entry`
where account = %s and against_voucher_type = 'Loan' and against_voucher = %s
""", (doc.payment_account, doc.name), as_dict=1)[0]
def check_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods):
disbursement_date = None
if not disbursement or disbursement.disbursed_amount == 0:
status = "Sanctioned"
elif disbursement.disbursed_amount == doc.loan_amount:
disbursement_date = disbursement.posting_date
status = "Disbursed"
elif disbursement.disbursed_amount > doc.loan_amount:
frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount))
if status == 'Disbursed' and getdate(disbursement_date) > getdate(frappe.db.get_value("Loan", doc.name, "repayment_start_date")):
frappe.throw(_("Disbursement Date cannot be after Loan Repayment Start Date"))
frappe.db.sql("""
update `tabLoan`
set status = %s, disbursement_date = %s
where name = %s
""", (status, disbursement_date, doc.name))
def validate_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods):
if repayment_method == "Repay Over Number of Periods" and not repayment_periods:
frappe.throw(_("Please enter Repayment Periods"))
@@ -222,4 +237,4 @@ def make_jv_entry(loan, company, loan_account, applicant_type, applicant, loan_a
"reference_name": loan,
})
journal_entry.set("accounts", account_amt_list)
return journal_entry.as_dict()
return journal_entry.as_dict()

View File

@@ -23,9 +23,8 @@ frappe.ui.form.on('Loan Application', {
},
add_toolbar_buttons: function(frm) {
if (frm.doc.status == "Approved") {
frm.add_custom_button(__('Loan'), function() {
frm.add_custom_button(__('Create Loan'), function() {
frappe.call({
type: "GET",
method: "erpnext.hr.doctype.loan_application.loan_application.make_loan",
args: {
"source_name": frm.doc.name
@@ -37,7 +36,7 @@ frappe.ui.form.on('Loan Application', {
}
}
});
})
}).addClass("btn-primary");
}
}
});

View File

@@ -9,11 +9,11 @@ from frappe.utils import flt, rounded
from frappe.model.mapper import get_mapped_doc
from frappe.model.document import Document
from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, check_repayment_method
from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, validate_repayment_method
class LoanApplication(Document):
def validate(self):
check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
self.validate_loan_amount()
self.get_repayment_details()
@@ -29,14 +29,17 @@ class LoanApplication(Document):
if self.repayment_method == "Repay Fixed Amount per Period":
monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
if monthly_interest_rate:
self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
math.log(self.repayment_amount - (self.loan_amount*monthly_interest_rate))) /
(math.log(1 + monthly_interest_rate)))
min_repayment_amount = self.loan_amount*monthly_interest_rate
if (self.repayment_amount - min_repayment_amount) <= 0:
frappe.throw(_("Repayment Amount must be greater than " \
+ str(flt(min_repayment_amount, 2))))
self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
math.log(self.repayment_amount - min_repayment_amount)) /(math.log(1 + monthly_interest_rate)))
else:
self.repayment_periods = self.loan_amount / self.repayment_amount
self.calculate_payable_amount()
def calculate_payable_amount(self):
balance_amount = self.loan_amount
self.total_payable_amount = 0
@@ -47,18 +50,21 @@ class LoanApplication(Document):
balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount)
self.total_payable_interest += interest_amount
self.total_payable_amount = self.loan_amount + self.total_payable_interest
@frappe.whitelist()
def make_loan(source_name, target_doc = None):
doclist = get_mapped_doc("Loan Application", source_name, {
"Loan Application": {
"doctype": "Loan",
"field_map": {
"repayment_amount": "monthly_repayment_amount"
},
"validation": {
"docstatus": ["=", 1]
}
}
}, target_doc)
return doclist
return doclist

View File

@@ -31,21 +31,22 @@ class TestLoanApplication(unittest.TestCase):
"rate_of_interest": 9.2,
"loan_amount": 250000,
"repayment_method": "Repay Over Number of Periods",
"repayment_periods": 24
"repayment_periods": 18
})
loan_application.insert()
def test_loan_totals(self):
loan_application = frappe.get_doc("Loan Application", {"applicant":self.applicant})
self.assertEquals(loan_application.repayment_amount, 11445)
self.assertEquals(loan_application.total_payable_interest, 24657)
self.assertEquals(loan_application.total_payable_amount, 274657)
loan_application.repayment_method = "Repay Fixed Amount per Period"
loan_application.repayment_amount = 15000
self.assertEqual(loan_application.total_payable_interest, 18599)
self.assertEqual(loan_application.total_payable_amount, 268599)
self.assertEqual(loan_application.repayment_amount, 14923)
loan_application.repayment_periods = 24
loan_application.save()
loan_application.reload()
self.assertEqual(loan_application.repayment_periods, 18)
self.assertEqual(loan_application.total_payable_interest, 18506)
self.assertEqual(loan_application.total_payable_amount, 268506)
self.assertEqual(loan_application.total_payable_interest, 24657)
self.assertEqual(loan_application.total_payable_amount, 274657)
self.assertEqual(loan_application.repayment_amount, 11445)

View File

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

View File

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

View File

@@ -92,5 +92,5 @@ def make_company():
company.abbr = "_TC10"
company.parent_company = "_Test Company"
company.default_currency = "INR"
company.country = "India"
company.country = "Pakistan"
company.insert()

View File

@@ -516,10 +516,14 @@ class BOM(WebsiteGenerator):
return erpnext.get_company_currency(self.company)
def add_to_cur_exploded_items(self, args):
if self.cur_exploded_items.get(args.item_code):
self.cur_exploded_items[args.item_code]["stock_qty"] += args.stock_qty
key = (args.item_code)
if args.operation:
key = (args.item_code, args.operation)
if key in self.cur_exploded_items:
self.cur_exploded_items[key]["stock_qty"] += args.stock_qty
else:
self.cur_exploded_items[args.item_code] = args
self.cur_exploded_items[key] = args
def get_child_exploded_items(self, bom_no, stock_qty):
""" Add all items from Flat BOM of child BOM"""
@@ -609,7 +613,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
and bom.name = %(bom)s
and item.is_stock_item in (1, {is_stock_item})
{where_conditions}
group by item_code, stock_uom
group by item_code, stock_uom {groupby_columns}
order by idx"""
is_stock_item = 0 if include_non_stock_items else 1
@@ -619,26 +623,32 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
is_stock_item=is_stock_item,
qty_field="stock_qty",
select_columns = """, bom_item.source_warehouse, bom_item.operation, bom_item.include_item_in_manufacturing,
(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""")
(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""",
groupby_columns = """, bom_item.operation""")
items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
elif fetch_scrap_items:
query = query.format(table="BOM Scrap Item", where_conditions="", select_columns=", bom_item.idx", is_stock_item=is_stock_item, qty_field="stock_qty")
query = query.format(table="BOM Scrap Item", where_conditions="", select_columns=", bom_item.idx", is_stock_item=is_stock_item, qty_field="stock_qty", groupby_columns="")
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
else:
query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item,
qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty",
select_columns = ", bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing")
select_columns = ", bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing",
groupby_columns = """, bom_item.operation""")
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
for item in items:
if item.item_code in item_dict:
item_dict[item.item_code]["qty"] += flt(item.qty)
key = (item.item_code)
if item.operation:
key = (item.item_code, item.operation)
if key in item_dict:
item_dict[key]["qty"] += flt(item.qty)
else:
item_dict[item.item_code] = item
item_dict[key] = item
for item, item_details in item_dict.items():
for d in [["Account", "expense_account", "default_expense_account"],
for d in [["Account", "expense_account", "stock_adjustment_account"],
["Cost Center", "cost_center", "cost_center"], ["Warehouse", "default_warehouse", ""]]:
company_in_record = frappe.db.get_value(d[0], item_details.get(d[1]), "company")
if not item_details.get(d[1]) or (company_in_record and company != company_in_record):

View File

@@ -105,7 +105,6 @@ class JobCard(Document):
for_quantity, time_in_mins = 0, 0
from_time_list, to_time_list = [], []
for d in frappe.get_all('Job Card',
filters = {'docstatus': 1, 'operation_id': self.operation_id}):
doc = frappe.get_doc('Job Card', d.name)
@@ -125,8 +124,8 @@ class JobCard(Document):
if data.name == self.operation_id:
data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins
data.actual_start_time = min(from_time_list)
data.actual_end_time = max(to_time_list)
data.actual_start_time = min(from_time_list) if from_time_list else None
data.actual_end_time = max(to_time_list) if to_time_list else None
wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status()

View File

@@ -97,7 +97,7 @@ class ProductionPlan(Document):
self.get_mr_items()
def get_so_items(self):
so_list = [d.sales_order for d in self.sales_orders if d.sales_order]
so_list = [d.sales_order for d in self.get("sales_orders", []) if d.sales_order]
if not so_list:
msgprint(_("Please enter Sales Orders in the above table"))
return []
@@ -132,7 +132,7 @@ class ProductionPlan(Document):
self.calculate_total_planned_qty()
def get_mr_items(self):
mr_list = [d.material_request for d in self.material_requests if d.material_request]
mr_list = [d.material_request for d in self.get("material_requests", []) if d.material_request]
if not mr_list:
msgprint(_("Please enter Material Requests in the above table"))
return []

View File

@@ -6,8 +6,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"letter_head": "Gadgets International",
"modified": "2018-05-28 16:22:24.040106",
"modified": "2018-06-28 16:22:24.040106",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Variance Report",

View File

@@ -506,6 +506,7 @@ erpnext.patches.v10_0.update_hub_connector_domain
erpnext.patches.v10_0.set_student_party_type
erpnext.patches.v10_0.update_project_in_sle
erpnext.patches.v10_0.fix_reserved_qty_for_sub_contract
erpnext.patches.v10_0.repost_requested_qty_for_non_stock_uom_items
erpnext.patches.v11_0.merge_land_unit_with_location
erpnext.patches.v11_0.add_index_on_nestedset_doctypes
erpnext.patches.v11_0.remove_modules_setup_page
@@ -602,4 +603,6 @@ erpnext.patches.v11_1.delete_scheduling_tool
erpnext.patches.v11_1.update_bank_transaction_status
erpnext.patches.v11_1.renamed_delayed_item_report
erpnext.patches.v11_1.set_missing_opportunity_from
erpnext.patches.v11_1.set_quotation_status
erpnext.patches.v11_1.set_quotation_status
erpnext.patches.v11_1.update_default_supplier_in_item_defaults
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture

View File

@@ -0,0 +1,21 @@
# Copyright (c) 2019, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty
count=0
for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse
from `tabMaterial Request Item` where docstatus = 1 and stock_uom<>uom"""):
try:
count += 1
update_bin_qty(item_code, warehouse, {
"indented_qty": get_indented_qty(item_code, warehouse),
})
if count % 200 == 0:
frappe.db.commit()
except:
frappe.db.rollback()

View File

@@ -0,0 +1,9 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.db.sql("""
update `tabMaterial Request`
set status='Manufactured'
where docstatus=1 and material_request_type='Manufacture' and per_ordered=100 and status != 'Stopped'
""")

View File

@@ -0,0 +1,25 @@
# Copyright (c) 2018, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
'''
default supplier was not set in the item defaults for multi company instance,
this patch will set the default supplier
'''
if not frappe.db.has_column('Item', 'default_supplier'):
return
frappe.reload_doc('stock', 'doctype', 'item_default')
frappe.reload_doc('stock', 'doctype', 'item')
companies = frappe.get_all("Company")
if len(companies) > 1:
frappe.db.sql(""" UPDATE `tabItem Default`, `tabItem`
SET `tabItem Default`.default_supplier = `tabItem`.default_supplier
WHERE
`tabItem Default`.parent = `tabItem`.name and `tabItem Default`.default_supplier is null
and `tabItem`.default_supplier is not null and `tabItem`.default_supplier != '' """)

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