Merge branch 'develop' into rfq-contact-email-set

This commit is contained in:
Marica 2021-02-10 12:02:14 +05:30 committed by GitHub
commit bb87950e96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
627 changed files with 24606 additions and 13512 deletions

View File

@ -92,6 +92,7 @@
"cur_page": true,
"cur_list": true,
"cur_tree": true,
"cur_pos": true,
"msg_dialog": true,
"is_null": true,
"in_list": true,
@ -149,6 +150,7 @@
"it": true,
"context": true,
"before": true,
"beforeEach": true
"beforeEach": true,
"onScan": true
}
}

View File

@ -2,7 +2,7 @@ import re
import sys
errors_encounter = 0
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
f_string_pattern = re.compile(r"_\(f[\"']")
@ -28,7 +28,7 @@ for _file in files_to_scan:
has_f_string = f_string_pattern.search(line)
if has_f_string:
errors_encounter += 1
print(f'\nF-strings are not supported for translations at line number {line_number + 1}\n{line.strip()[:100]}')
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
continue
else:
continue
@ -36,7 +36,7 @@ for _file in files_to_scan:
match = pattern.search(line)
error_found = False
if not match and line.endswith(',\n'):
if not match and line.endswith((',\n', '[\n')):
# concat remaining text to validate multiline pattern
line = "".join(file_lines[line_number - 1:])
line = line[start_matches.start() + 1:]
@ -44,11 +44,11 @@ for _file in files_to_scan:
if not match:
error_found = True
print(f'\nTranslation syntax error at line number {line_number + 1}\n{line.strip()[:100]}')
print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}')
if not error_found and not words_pattern.search(line):
error_found = True
print(f'\nTranslation is useless because it has no words at line number {line_number + 1}\n{line.strip()[:100]}')
print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}')
if error_found:
errors_encounter += 1

9
erpnext/.stylelintrc Normal file
View File

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

View File

@ -132,16 +132,10 @@ def allow_regional(fn):
return caller
def get_last_membership():
def get_last_membership(member):
'''Returns last membership if exists'''
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
dict(member=member, paid=1), order_by='to_date desc', limit=1)
return last_membership and last_membership[0]
def is_member():
'''Returns true if the user is still a member'''
last_membership = get_last_membership()
if last_membership and getdate(last_membership.to_date) > getdate():
return True
return False
if last_membership:
return last_membership[0]

View File

@ -1,161 +0,0 @@
{
"cards": [
{
"hidden": 0,
"label": "Accounting Masters",
"links": "[\n {\n \"description\": \"Company (not Customer or Supplier) master.\",\n \"label\": \"Company\",\n \"name\": \"Company\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of financial accounts.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Chart of Accounts\",\n \"name\": \"Account\",\n \"onboard\": 1,\n \"route\": \"#Tree/Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Accounts Settings\",\n \"name\": \"Accounts Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Financial / accounting year.\",\n \"label\": \"Fiscal Year\",\n \"name\": \"Fiscal Year\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Accounting Dimension\",\n \"name\": \"Accounting Dimension\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Finance Book\",\n \"name\": \"Finance Book\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Accounting Period\",\n \"name\": \"Accounting Period\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Payment Terms based on conditions\",\n \"label\": \"Payment Term\",\n \"name\": \"Payment Term\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "General Ledger",
"links": "[\n {\n \"description\": \"Accounting journal entries.\",\n \"label\": \"Journal Entry\",\n \"name\": \"Journal Entry\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Make journal entries from a template.\",\n \"label\": \"Journal Entry Template\",\n \"name\": \"Journal Entry Template\",\n \"type\": \"doctype\"\n },\n \n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"General Ledger\",\n \"name\": \"General Ledger\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Customer Ledger Summary\",\n \"name\": \"Customer Ledger Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Supplier Ledger Summary\",\n \"name\": \"Supplier Ledger Summary\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
"label": "Accounts Receivable",
"links": "[\n {\n \"description\": \"Bills raised to Customers.\",\n \"label\": \"Sales Invoice\",\n \"name\": \"Sales Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customer database.\",\n \"label\": \"Customer\",\n \"name\": \"Customer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bank/Cash transactions against party or for internal transfer\",\n \"label\": \"Payment Entry\",\n \"name\": \"Payment Entry\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Payment Request\",\n \"label\": \"Payment Request\",\n \"name\": \"Payment Request\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Receivable\",\n \"name\": \"Accounts Receivable\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Receivable Summary\",\n \"name\": \"Accounts Receivable Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Register\",\n \"name\": \"Sales Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Sales Register\",\n \"name\": \"Item-wise Sales Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Analysis\",\n \"name\": \"Sales Order Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Delivered Items To Be Billed\",\n \"name\": \"Delivered Items To Be Billed\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
"label": "Accounts Payable",
"links": "[\n {\n \"description\": \"Bills raised by Suppliers.\",\n \"label\": \"Purchase Invoice\",\n \"name\": \"Purchase Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Supplier database.\",\n \"label\": \"Supplier\",\n \"name\": \"Supplier\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bank/Cash transactions against party or for internal transfer\",\n \"label\": \"Payment Entry\",\n \"name\": \"Payment Entry\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Payable\",\n \"name\": \"Accounts Payable\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Payable Summary\",\n \"name\": \"Accounts Payable Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Register\",\n \"name\": \"Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase Register\",\n \"name\": \"Item-wise Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Purchase Order Analysis\",\n \"name\": \"Purchase Order Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Received Items To Be Billed\",\n \"name\": \"Received Items To Be Billed\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
"label": "Reports",
"links": "[\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Trial Balance for Party\",\n \"name\": \"Trial Balance for Party\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Payment Period Based On Invoice Date\",\n \"name\": \"Payment Period Based On Invoice Date\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Payment Summary\",\n \"name\": \"Sales Payment Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Address And Contacts\",\n \"name\": \"Address And Contacts\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"DATEV Export\",\n \"name\": \"DATEV\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
"label": "Financial Statements",
"links": "[\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Trial Balance\",\n \"name\": \"Trial Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Profit and Loss Statement\",\n \"name\": \"Profit and Loss Statement\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Balance Sheet\",\n \"name\": \"Balance Sheet\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Cash Flow\",\n \"name\": \"Cash Flow\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Consolidated Financial Statement\",\n \"name\": \"Consolidated Financial Statement\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
"label": "Multi Currency",
"links": "[\n {\n \"description\": \"Enable / disable currencies.\",\n \"label\": \"Currency\",\n \"name\": \"Currency\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Currency exchange rate master.\",\n \"label\": \"Currency Exchange\",\n \"name\": \"Currency Exchange\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Exchange Rate Revaluation master.\",\n \"label\": \"Exchange Rate Revaluation\",\n \"name\": \"Exchange Rate Revaluation\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Settings",
"links": "[\n {\n \"description\": \"Setup Gateway accounts.\",\n \"label\": \"Payment Gateway Account\",\n \"name\": \"Payment Gateway Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Template of terms or contract.\",\n \"label\": \"Terms and Conditions Template\",\n \"name\": \"Terms and Conditions\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"e.g. Bank, Cash, Credit Card\",\n \"label\": \"Mode of Payment\",\n \"name\": \"Mode of Payment\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Bank Statement",
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
"label": "Subscription Management",
"links": "[\n {\n \"label\": \"Subscription Plan\",\n \"name\": \"Subscription Plan\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Subscription\",\n \"name\": \"Subscription\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Subscription Settings\",\n \"name\": \"Subscription Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Goods and Services Tax (GST India)",
"links": "[\n {\n \"label\": \"GST Settings\",\n \"name\": \"GST Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"GST HSN Code\",\n \"name\": \"GST HSN Code\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-1\",\n \"name\": \"GSTR-1\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-2\",\n \"name\": \"GSTR-2\",\n \"type\": \"report\"\n },\n {\n \"label\": \"GSTR 3B Report\",\n \"name\": \"GSTR 3B Report\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Sales Register\",\n \"name\": \"GST Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Purchase Register\",\n \"name\": \"GST Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Sales Register\",\n \"name\": \"GST Itemised Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Purchase Register\",\n \"name\": \"GST Itemised Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"country\": \"India\",\n \"description\": \"C-Form records\",\n \"label\": \"C-Form\",\n \"name\": \"C-Form\",\n \"type\": \"doctype\"\n },\n {\n \"country\": \"India\",\n \"label\": \"Lower Deduction Certificate\",\n \"name\": \"Lower Deduction Certificate\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Share Management",
"links": "[\n {\n \"description\": \"List of available Shareholders with folio numbers\",\n \"label\": \"Shareholder\",\n \"name\": \"Shareholder\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of all share transactions\",\n \"label\": \"Share Transfer\",\n \"name\": \"Share Transfer\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Share Transfer\"\n ],\n \"doctype\": \"Share Transfer\",\n \"is_query_report\": true,\n \"label\": \"Share Ledger\",\n \"name\": \"Share Ledger\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Share Transfer\"\n ],\n \"doctype\": \"Share Transfer\",\n \"is_query_report\": true,\n \"label\": \"Share Balance\",\n \"name\": \"Share Balance\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
"label": "Cost Center and Budgeting",
"links": "[\n {\n \"description\": \"Tree of financial Cost Centers.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Chart of Cost Centers\",\n \"name\": \"Cost Center\",\n \"route\": \"#Tree/Cost Center\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Define budget for a financial year.\",\n \"label\": \"Budget\",\n \"name\": \"Budget\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Accounting Dimension\",\n \"name\": \"Accounting Dimension\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Cost Center\"\n ],\n \"doctype\": \"Cost Center\",\n \"is_query_report\": true,\n \"label\": \"Budget Variance Report\",\n \"name\": \"Budget Variance Report\",\n \"type\": \"report\"\n },\n {\n \"description\": \"Seasonality for setting budgets, targets etc.\",\n \"label\": \"Monthly Distribution\",\n \"name\": \"Monthly Distribution\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Opening and Closing",
"links": "[\n {\n \"label\": \"Opening Invoice Creation Tool\",\n \"name\": \"Opening Invoice Creation Tool\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Chart of Accounts Importer\",\n \"name\": \"Chart of Accounts Importer\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Close Balance Sheet and book Profit or Loss.\",\n \"label\": \"Period Closing Voucher\",\n \"name\": \"Period Closing Voucher\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Taxes",
"links": "[\n {\n \"description\": \"Tax template for selling transactions.\",\n \"label\": \"Sales Taxes and Charges Template\",\n \"name\": \"Sales Taxes and Charges Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax template for buying transactions.\",\n \"label\": \"Purchase Taxes and Charges Template\",\n \"name\": \"Purchase Taxes and Charges Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax template for item tax rates.\",\n \"label\": \"Item Tax Template\",\n \"name\": \"Item Tax Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax Category for overriding tax rates.\",\n \"label\": \"Tax Category\",\n \"name\": \"Tax Category\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax Rule for transactions.\",\n \"label\": \"Tax Rule\",\n \"name\": \"Tax Rule\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax Withholding rates to be applied on transactions.\",\n \"label\": \"Tax Withholding Category\",\n \"name\": \"Tax Withholding Category\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Profitability",
"links": "[\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Gross Profit\",\n \"name\": \"Gross Profit\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Profitability Analysis\",\n \"name\": \"Profitability Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Invoice Trends\",\n \"name\": \"Sales Invoice Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
"label": "Value-Added Tax (VAT UAE)",
"links": "[\n {\n \"country\": \"United Arab Emirates\",\n \"label\": \"UAE VAT Settings\",\n \"name\": \"UAE VAT Settings\",\n \"type\": \"doctype\"\n },\n {\n \"country\": \"United Arab Emirates\",\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n }\n\n]"
}
],
"category": "Modules",
"charts": [
{
"chart_name": "Profit and Loss",
"label": "Profit and Loss"
}
],
"creation": "2020-03-02 15:41:59.515192",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Accounting",
"modified": "2020-11-11 18:35:11.542909",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
"onboarding": "Accounts",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
"label": "Chart Of Accounts",
"link_to": "Account",
"type": "DocType"
},
{
"label": "Sales Invoice",
"link_to": "Sales Invoice",
"type": "DocType"
},
{
"label": "Purchase Invoice",
"link_to": "Purchase Invoice",
"type": "DocType"
},
{
"label": "Journal Entry",
"link_to": "Journal Entry",
"type": "DocType"
},
{
"label": "Payment Entry",
"link_to": "Payment Entry",
"type": "DocType"
},
{
"label": "Accounts Receivable",
"link_to": "Accounts Receivable",
"type": "Report"
},
{
"label": "General Ledger",
"link_to": "General Ledger",
"type": "Report"
},
{
"label": "Trial Balance",
"link_to": "Trial Balance",
"type": "Report"
},
{
"label": "Dashboard",
"link_to": "Accounts",
"type": "Dashboard"
}
]
}

View File

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

View File

@ -254,7 +254,8 @@ def create_account(**kwargs):
account_name = kwargs.get('account_name'),
account_type = kwargs.get('account_type'),
parent_account = kwargs.get('parent_account'),
company = kwargs.get('company')
company = kwargs.get('company'),
account_currency = kwargs.get('account_currency')
))
account.save()

View File

@ -2,7 +2,6 @@
// For license information, please see license.txt
frappe.ui.form.on('Accounting Dimension', {
refresh: function(frm) {
frm.set_query('document_type', () => {
let invalid_doctypes = frappe.model.core_doctypes_list;

View File

@ -30,6 +30,7 @@
"fieldtype": "Link",
"label": "Reference Document Type",
"options": "DocType",
"read_only_depends_on": "eval:!doc.__islocal",
"reqd": 1
},
{
@ -48,7 +49,7 @@
}
],
"links": [],
"modified": "2020-03-22 20:34:39.805728",
"modified": "2021-02-08 16:37:53.936656",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Dimension",

View File

@ -29,6 +29,16 @@ class AccountingDimension(Document):
if exists and self.is_new():
frappe.throw("Document Type already used as a dimension")
if not self.is_new():
self.validate_document_type_change()
def validate_document_type_change(self):
doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type")
if doctype_before_save != self.document_type:
message = _("Cannot change Reference Document Type.")
message += _("Please create a new Accounting Dimension if required.")
frappe.throw(message)
def after_insert(self):
if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
@ -165,9 +175,9 @@ def toggle_disabling(doc):
frappe.clear_cache(doctype=doctype)
def get_doctypes_with_dimensions():
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
doclist = ["GL Entry", "Sales Invoice", "POS Invoice", "Purchase Invoice", "Payment Entry", "Asset",
"Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
"Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Sales Invoice Item", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
@ -203,7 +213,7 @@ def get_dimension_with_children(doctype, dimension):
return all_dimensions
@frappe.whitelist()
def get_dimension_filters():
def get_dimensions(with_cost_center_and_project=False):
dimension_filters = frappe.db.sql("""
SELECT label, fieldname, document_type
FROM `tabAccounting Dimension`
@ -214,6 +224,18 @@ def get_dimension_filters():
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
WHERE c.parent = p.name""", as_dict=1)
if with_cost_center_and_project:
dimension_filters.extend([
{
'fieldname': 'cost_center',
'document_type': 'Cost Center'
},
{
'fieldname': 'project',
'document_type': 'Project'
}
])
default_dimensions_map = {}
for dimension in default_dimensions:
default_dimensions_map.setdefault(dimension.company, {})

View File

@ -11,37 +11,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import d
class TestAccountingDimension(unittest.TestCase):
def setUp(self):
frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
dimension = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Department",
}).insert()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
dimension1.disabled = 0
dimension1.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
dimension1 = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Location",
})
dimension1.append("dimension_defaults", {
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1
})
dimension1.insert()
dimension1.save()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
dimension1.disabled = 0
dimension1.save()
create_dimension()
def test_dimension_against_sales_invoice(self):
si = create_sales_invoice(do_not_save=1)
@ -101,6 +71,38 @@ class TestAccountingDimension(unittest.TestCase):
def tearDown(self):
disable_dimension()
def create_dimension():
frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Department",
}).insert()
else:
dimension = frappe.get_doc("Accounting Dimension", "Department")
dimension.disabled = 0
dimension.save()
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
dimension1 = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Location",
})
dimension1.append("dimension_defaults", {
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1
})
dimension1.insert()
dimension1.save()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
dimension1.disabled = 0
dimension1.save()
def disable_dimension():
dimension1 = frappe.get_doc("Accounting Dimension", "Department")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,6 +21,7 @@
"book_asset_depreciation_entry_automatically",
"add_taxes_from_item_tax_template",
"automatically_fetch_payment_terms",
"delete_linked_ledger_entries",
"deferred_accounting_settings_section",
"automatically_process_deferred_accounting_entry",
"book_deferred_entries_based_on",
@ -219,6 +220,12 @@
"fieldtype": "Select",
"label": "Book Deferred Entries Based On",
"options": "Days\nMonths"
},
{
"default": "0",
"fieldname": "delete_linked_ledger_entries",
"fieldtype": "Check",
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
}
],
"icon": "icon-cog",
@ -226,7 +233,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2020-10-13 11:32:52.268826",
"modified": "2021-01-05 13:04:00.118892",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@ -254,4 +261,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}
}

View File

@ -0,0 +1,43 @@
{
"actions": [],
"creation": "2020-11-08 18:22:36.001131",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"accounting_dimension",
"dimension_value"
],
"fields": [
{
"fieldname": "accounting_dimension",
"fieldtype": "Link",
"label": "Accounting Dimension",
"options": "DocType",
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "dimension_value",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"options": "accounting_dimension",
"show_days": 1,
"show_seconds": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-11-23 09:56:19.744200",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Allowed Dimension",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@ -0,0 +1,46 @@
{
"actions": [],
"creation": "2020-11-08 18:20:00.944449",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"applicable_on_account",
"is_mandatory"
],
"fields": [
{
"fieldname": "applicable_on_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Accounts",
"options": "Account",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"columns": 2,
"default": "0",
"fieldname": "is_mandatory",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Mandatory",
"show_days": 1,
"show_seconds": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-11-22 19:55:13.324136",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Applicable On Account",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@ -1,5 +1,6 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.provide('erpnext.integrations');
frappe.ui.form.on('Bank', {
onload: function(frm) {
@ -20,7 +21,12 @@ frappe.ui.form.on('Bank', {
frm.set_df_property('address_and_contact', 'hidden', 0);
frappe.contacts.render_address_and_contact(frm);
}
},
if (frm.doc.plaid_access_token) {
frm.add_custom_button(__('Refresh Plaid Link'), () => {
new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token);
});
}
}
});
@ -40,4 +46,79 @@ let add_fields_to_mapping_table = function (frm) {
frm.doc.name).options = options;
frm.fields_dict.bank_transaction_mapping.grid.refresh();
};
};
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
constructor(access_token) {
this.access_token = access_token;
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
this.init_config();
}
async init_config() {
this.plaid_env = await frappe.db.get_single_value('Plaid Settings', 'plaid_env');
this.token = await this.get_link_token_for_update();
this.init_plaid();
}
async get_link_token_for_update() {
const token = frappe.xcall(
'erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_link_token_for_update',
{ access_token: this.access_token }
)
if (!token) {
frappe.throw(__('Cannot retrieve link token for update. Check Error Log for more information'));
}
return token;
}
init_plaid() {
const me = this;
me.loadScript(me.plaidUrl)
.then(() => {
me.onScriptLoaded(me);
})
.then(() => {
if (me.linkHandler) {
me.linkHandler.open();
}
})
.catch((error) => {
me.onScriptError(error);
});
}
loadScript(src) {
return new Promise(function (resolve, reject) {
if (document.querySelector("script[src='" + src + "']")) {
resolve();
return;
}
const el = document.createElement('script');
el.type = 'text/javascript';
el.async = true;
el.src = src;
el.addEventListener('load', resolve);
el.addEventListener('error', reject);
el.addEventListener('abort', reject);
document.head.appendChild(el);
});
}
onScriptLoaded(me) {
me.linkHandler = Plaid.create({
env: me.plaid_env,
token: me.token,
onSuccess: me.plaid_success
});
}
onScriptError(error) {
frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
console.log(error);
}
plaid_success(token, response) {
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
}
};

View File

@ -1,24 +1,9 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Budget', {
onload: function(frm) {
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company
}
}
})
frm.set_query("project", function() {
return {
filters: {
company: frm.doc.company
}
}
})
frm.set_query("account", "accounts", function() {
return {
filters: {
@ -26,16 +11,18 @@ frappe.ui.form.on('Budget', {
report_type: "Profit and Loss",
is_group: 0
}
}
})
};
});
frm.set_query("monthly_distribution", function() {
return {
filters: {
fiscal_year: frm.doc.fiscal_year
}
}
})
};
});
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
refresh: function(frm) {

View File

@ -122,8 +122,10 @@ class TestBudget(unittest.TestCase):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
project = frappe.get_value("Project", {"project_name": "_Test Project"})
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project=project, posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit)
@ -147,8 +149,11 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Project")
project = frappe.get_value("Project", {"project_name": "_Test Project"})
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC",
project=project, posting_date=nowdate())
self.assertRaises(BudgetError, jv.submit)
@ -159,10 +164,10 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
month = now_datetime().month
if month > 10:
month = 10
if month > 9:
month = 9
for i in range(month):
for i in range(month+1):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
@ -181,12 +186,14 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Project")
month = now_datetime().month
if month > 10:
month = 10
if month > 9:
month = 9
for i in range(month):
project = frappe.get_value("Project", {"project_name": "_Test Project"})
for i in range(month + 1):
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True,
project=project)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
@ -289,7 +296,7 @@ def make_budget(**args):
budget = frappe.new_doc("Budget")
if budget_against == "Project":
budget.project = "_Test Project"
budget.project = frappe.get_value("Project", {"project_name": "_Test Project"})
else:
budget.cost_center =cost_center or "_Test Cost Center - _TC"

View File

@ -11,8 +11,10 @@ from frappe.model.meta import get_field_precision
from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled
from erpnext.accounts.utils import get_account_currency
from erpnext.accounts.utils import get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency
from erpnext.exceptions import InvalidAccountCurrency, InvalidAccountDimensionError, MandatoryAccountDimensionError
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
from six import iteritems
exclude_from_linked_with = True
class GLEntry(Document):
@ -39,6 +41,7 @@ class GLEntry(Document):
if not from_repost:
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()
validate_frozen_account(self.account, adv_adj)
validate_balance_type(self.account, adv_adj)
@ -76,11 +79,9 @@ class GLEntry(Document):
.format(self.voucher_type, self.voucher_no, self.account))
def validate_dimensions_for_pl_and_bs(self):
account_type = frappe.db.get_value("Account", self.account, "report_type")
for dimension in get_checks_for_pl_and_bs_accounts():
if account_type == "Profit and Loss" \
and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
if not self.get(dimension.fieldname):
@ -93,6 +94,25 @@ class GLEntry(Document):
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.")
.format(dimension.label, self.account))
def validate_allowed_dimensions(self):
dimension_filter_map = get_dimension_filter_map()
for key, value in iteritems(dimension_filter_map):
dimension = key[0]
account = key[1]
if self.account == account:
if value['is_mandatory'] and not self.get(dimension):
frappe.throw(_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), MandatoryAccountDimensionError)
if value['allow_or_restrict'] == 'Allow':
if self.get(dimension) and self.get(dimension) not in value['allowed_dimensions']:
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
else:
if self.get(dimension) and self.get(dimension) in value['allowed_dimensions']:
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
def check_pl_account(self):
if self.is_opening=='Yes' and \
@ -137,9 +157,10 @@ class GLEntry(Document):
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
if self.cost_center and _check_is_group():
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""")
.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
and self.cost_center and _check_is_group():
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
def validate_party(self):
validate_party_frozen_disabled(self.party_type, self.party)
@ -149,7 +170,7 @@ class GLEntry(Document):
account_currency = get_account_currency(self.account)
if not self.account_currency:
self.account_currency = company_currency
self.account_currency = account_currency or company_currency
if account_currency != self.account_currency:
frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}")

View File

@ -20,7 +20,8 @@ def get_data():
'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
},
{
'items': ['Item']
'label': _('Stock'),
'items': ['Item Groups', 'Item']
}
]
}

View File

@ -120,6 +120,8 @@ frappe.ui.form.on("Journal Entry", {
}
}
});
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
voucher_type: function(frm){
@ -197,6 +199,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
this.load_defaults();
this.setup_queries();
this.setup_balance_formatter();
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
},
onload_post_render: function() {
@ -222,15 +225,6 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
return erpnext.journal_entry.account_query(me.frm);
});
me.frm.set_query("cost_center", "accounts", function(doc, cdt, cdn) {
return {
filters: {
company: me.frm.doc.company,
is_group: 0
}
};
});
me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
const row = locals[cdt][cdn];
@ -406,6 +400,8 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
}
}
cur_frm.cscript.update_totals(doc);
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'accounts');
},
});

View File

@ -160,7 +160,7 @@ class TestJournalEntry(unittest.TestCase):
self.assertFalse(gle)
def test_reverse_journal_entry(self):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
jv = make_journal_entry("_Test Bank USD - _TC",
"Sales - _TC", 100, exchange_rate=50, save=False)
@ -299,15 +299,20 @@ class TestJournalEntry(unittest.TestCase):
def test_jv_with_project(self):
from erpnext.projects.doctype.project.test_project import make_project
project = make_project({
'project_name': 'Journal Entry Project',
'project_template_name': 'Test Project Template',
'start_date': '2020-01-01'
})
if not frappe.db.exists("Project", {"project_name": "Journal Entry Project"}):
project = make_project({
'project_name': 'Journal Entry Project',
'project_template_name': 'Test Project Template',
'start_date': '2020-01-01'
})
project_name = project.name
else:
project_name = frappe.get_value("Project", {"project_name": "_Test Project"})
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
for d in jv.accounts:
d.project = project.project_name
d.project = project_name
jv.voucher_type = "Bank Entry"
jv.multi_currency = 0
jv.cheque_no = "112233"
@ -317,10 +322,10 @@ class TestJournalEntry(unittest.TestCase):
expected_values = {
"_Test Cash - _TC": {
"project": project.project_name
"project": project_name
},
"_Test Bank - _TC": {
"project": project.project_name
"project": project_name
}
}

View File

@ -1,6 +1,8 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Loyalty Program', {
setup: function(frm) {
var help_content =
@ -46,20 +48,17 @@ frappe.ui.form.on('Loyalty Program', {
};
});
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company
}
};
});
frm.set_value("company", frappe.defaults.get_user_default("Company"));
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
refresh: function(frm) {
if (frm.doc.loyalty_program_type === "Single Tier Program" && frm.doc.collection_rules.length > 1) {
frappe.throw(__("Please select the Multiple Tier Program type for more than one collection rules."));
}
},
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
}
});

View File

@ -36,6 +36,8 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
frm.page.set_indicator(__('In Progress'), 'orange');
});
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
refresh: function(frm) {
@ -100,6 +102,7 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
}
})
}
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
invoice_type: function(frm) {
@ -118,7 +121,8 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
frappe.render_template('opening_invoice_creation_tool_dashboard', {
data: opening_invoices_summary,
max_count: max_count
})
}),
__("Opening Invoices Summary")
);
section.on('click', '.invoice-link', function() {

View File

@ -64,11 +64,11 @@ class OpeningInvoiceCreationTool(Document):
prepare_invoice_summary(doctype, invoices)
return invoices_summary, max_count
def validate_company(self):
if not self.company:
frappe.throw(_("Please select the Company"))
def set_missing_values(self, row):
row.qty = row.qty or 1.0
row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(self.company)
@ -210,7 +210,7 @@ def start_import(invoices):
frappe.db.commit()
if errors:
frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details")
.format(errors, "<a href='#List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
.format(errors, "<a href='/app/List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
return names
def publish(index, total, doctype):

View File

@ -1,4 +1,3 @@
<h5 style="margin-top: 0px;">{{ __("Opening Invoices Summary") }}</h5>
{% $.each(data, (company, summary) => { %}
<h6 style="margin: 15px 0px -10px 0px;"><a class="company-link"> {{ company }}</a></h6>

View File

@ -1,6 +1,7 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
{% include "erpnext/public/js/controllers/accounts.js" %}
frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
@ -8,6 +9,8 @@ frappe.ui.form.on('Payment Entry', {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
}
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
setup: function(frm) {
@ -88,15 +91,6 @@ frappe.ui.form.on('Payment Entry', {
}
});
frm.set_query("cost_center", "deductions", function() {
return {
filters: {
"is_group": 0,
"company": frm.doc.company
}
}
});
frm.set_query("reference_doctype", "references", function() {
if (frm.doc.party_type=="Customer") {
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
@ -167,6 +161,7 @@ frappe.ui.form.on('Payment Entry', {
company: function(frm) {
frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm);
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
contact_person: function(frm) {
@ -401,6 +396,8 @@ frappe.ui.form.on('Payment Entry', {
set_account_currency_and_balance: function(frm, account, currency_field,
balance_field, callback_function) {
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.posting_date && account) {
frappe.call({
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
@ -427,6 +424,14 @@ frappe.ui.form.on('Payment Entry', {
if(!frm.doc.paid_amount && frm.doc.received_amount)
frm.events.received_amount(frm);
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
&& frm.doc.paid_amount != frm.doc.received_amount) {
if (company_currency != frm.doc.paid_from_account_currency &&
frm.doc.payment_type == "Pay") {
frm.doc.paid_amount = frm.doc.received_amount;
}
}
}
},
() => {

View File

@ -88,19 +88,19 @@ class PaymentReconciliation(Document):
voucher_type = ('Sales Invoice'
if self.party_type == 'Customer' else "Purchase Invoice")
return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type,
(sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount,
return frappe.db.sql(""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type,
(sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount,
account_currency as currency
FROM `tab{doc}`, `tabGL Entry`
FROM `tab{doc}` doc, `tabGL Entry` gl
WHERE
(`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no)
and `tab{doc}`.{party_type_field} = %(party)s
and `tab{doc}`.is_return = 1 and `tab{doc}`.return_against IS NULL
and `tabGL Entry`.against_voucher_type = %(voucher_type)s
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
and `tabGL Entry`.is_cancelled = 0
GROUP BY `tab{doc}`.name
(doc.name = gl.against_voucher or doc.name = gl.voucher_no)
and doc.{party_type_field} = %(party)s
and doc.is_return = 1 and ifnull(doc.return_against, "") = ""
and gl.against_voucher_type = %(voucher_type)s
and doc.docstatus = 1 and gl.party = %(party)s
and gl.party_type = %(party_type)s and gl.account = %(account)s
and gl.is_cancelled = 0
GROUP BY doc.name
Having
amount > 0
""".format(
@ -113,7 +113,7 @@ class PaymentReconciliation(Document):
'party_type': self.party_type,
'voucher_type': voucher_type,
'account': self.receivable_payable_account
}, as_dict=1)
}, as_dict=1, debug=1)
def add_payment_entries(self, entries):
self.set('payments', [])

View File

@ -2,7 +2,7 @@ frappe.listview_settings['Payment Request'] = {
add_fields: ["status"],
get_indicator: function(doc) {
if(doc.status == "Draft") {
return [__("Draft"), "darkgrey", "status,=,Draft"];
return [__("Draft"), "gray", "status,=,Draft"];
}
if(doc.status == "Requested") {
return [__("Requested"), "green", "status,=,Requested"];
@ -19,5 +19,5 @@ frappe.listview_settings['Payment Request'] = {
else if(doc.status == "Cancelled") {
return [__("Cancelled"), "red", "status,=,Cancelled"];
}
}
}
}

View File

@ -8,7 +8,7 @@ from frappe import _
from erpnext.accounts.utils import get_account_currency
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
get_dimension_filters)
get_dimensions)
class PeriodClosingVoucher(AccountsController):
def validate(self):
@ -58,7 +58,7 @@ class PeriodClosingVoucher(AccountsController):
for dimension in accounting_dimensions:
dimension_fields.append('t1.{0}'.format(dimension))
dimension_filters, default_dimensions = get_dimension_filters()
dimension_filters, default_dimensions = get_dimensions()
pl_accounts = self.get_pl_balances(dimension_fields)

View File

@ -3,6 +3,7 @@
frappe.ui.form.on('POS Closing Entry', {
onload: function(frm) {
frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
frm.set_query("pos_profile", function(doc) {
return {
filters: { 'user': doc.user }

View File

@ -6,11 +6,13 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"period_details_section",
"period_start_date",
"period_end_date",
"column_break_3",
"posting_date",
"pos_opening_entry",
"status",
"section_break_5",
"company",
"column_break_7",
@ -64,7 +66,8 @@
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "User Details"
},
{
"fieldname": "company",
@ -120,7 +123,7 @@
"collapsible_depends_on": "eval:doc.docstatus==0",
"fieldname": "section_break_13",
"fieldtype": "Section Break",
"label": "Details"
"label": "Totals"
},
{
"default": "0",
@ -184,11 +187,32 @@
"label": "POS Opening Entry",
"options": "POS Opening Entry",
"reqd": 1
},
{
"allow_on_submit": 1,
"default": "Draft",
"fieldname": "status",
"fieldtype": "Select",
"hidden": 1,
"label": "Status",
"options": "Draft\nSubmitted\nQueued\nCancelled",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "period_details_section",
"fieldtype": "Section Break",
"label": "Period Details"
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-05-29 15:03:22.226113",
"links": [
{
"link_doctype": "POS Invoice Merge Log",
"link_fieldname": "pos_closing_entry"
}
],
"modified": "2021-02-01 13:47:20.722104",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry",

View File

@ -6,13 +6,12 @@ from __future__ import unicode_literals
import frappe
import json
from frappe import _
from frappe.model.document import Document
from frappe.utils import getdate, get_datetime, flt
from collections import defaultdict
from frappe.utils import get_datetime, flt
from erpnext.controllers.status_updater import StatusUpdater
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices, unconsolidate_pos_invoices
class POSClosingEntry(Document):
class POSClosingEntry(StatusUpdater):
def validate(self):
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
@ -57,20 +56,29 @@ class POSClosingEntry(Document):
if not invalid_rows:
return
error_list = [_("Row #{}: {}").format(row.get('idx'), row.get('msg')) for row in invalid_rows]
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
error_list = []
for row in invalid_rows:
for msg in row.get('msg'):
error_list.append(_("Row #{}: {}").format(row.get('idx'), msg))
def on_submit(self):
merge_pos_invoices(self.pos_transactions)
opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
opening_entry.pos_closing_entry = self.name
opening_entry.set_status()
opening_entry.save()
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
def get_payment_reconciliation_details(self):
currency = frappe.get_cached_value('Company', self.company, "default_currency")
return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
{"data": self, "currency": currency})
def on_submit(self):
consolidate_pos_invoices(closing_entry=self)
def on_cancel(self):
unconsolidate_pos_invoices(closing_entry=self)
def update_opening_entry(self, for_cancel=False):
opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
opening_entry.pos_closing_entry = self.name if not for_cancel else None
opening_entry.set_status()
opening_entry.save()
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs

View File

@ -0,0 +1,16 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
// render
frappe.listview_settings['POS Closing Entry'] = {
get_indicator: function(doc) {
var status_color = {
"Draft": "red",
"Submitted": "blue",
"Queued": "orange",
"Cancelled": "red"
};
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
}
};

View File

@ -13,7 +13,6 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi
class TestPOSClosingEntry(unittest.TestCase):
def test_pos_closing_entry(self):
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
@ -45,6 +44,49 @@ class TestPOSClosingEntry(unittest.TestCase):
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
def test_cancelling_of_pos_closing_entry(self):
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
pos_inv1.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500
})
pos_inv1.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
})
pos_inv2.submit()
pcv_doc = make_closing_entry_from_opening(opening_entry)
payment = pcv_doc.payment_reconciliation[0]
self.assertEqual(payment.mode_of_payment, 'Cash')
for d in pcv_doc.payment_reconciliation:
if d.mode_of_payment == 'Cash':
d.closing_amount = 6700
pcv_doc.submit()
pos_inv1.load_from_db()
self.assertRaises(frappe.ValidationError, pos_inv1.cancel)
si_doc = frappe.get_doc("Sales Invoice", pos_inv1.consolidated_invoice)
self.assertRaises(frappe.ValidationError, si_doc.cancel)
pcv_doc.load_from_db()
pcv_doc.cancel()
si_doc.load_from_db()
pos_inv1.load_from_db()
self.assertEqual(si_doc.docstatus, 2)
self.assertEqual(pos_inv1.status, 'Paid')
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
def init_user_and_profile(**args):
user = 'test@example.com'
test_user = frappe.get_doc('User', user)

View File

@ -2,6 +2,7 @@
// For license information, please see license.txt
{% include 'erpnext/selling/sales_common.js' %};
frappe.provide("erpnext.accounts");
erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({
setup(doc) {
@ -9,12 +10,19 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
this._super(doc);
},
company: function() {
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
},
onload(doc) {
this._super();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
this.frm.script_manager.trigger("is_pos");
this.frm.refresh_fields();
}
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
},
refresh(doc) {

View File

@ -13,11 +13,11 @@
"customer",
"customer_name",
"tax_id",
"is_pos",
"pos_profile",
"offline_pos_name",
"is_return",
"consolidated_invoice",
"is_pos",
"is_return",
"update_billed_amount_in_sales_order",
"column_break1",
"company",
"posting_date",
@ -25,10 +25,7 @@
"set_posting_time",
"due_date",
"amended_from",
"returns",
"return_against",
"column_break_21",
"update_billed_amount_in_sales_order",
"accounting_dimensions_section",
"project",
"dimension_col_break",
@ -183,8 +180,7 @@
"column_break_140",
"auto_repeat",
"update_auto_repeat_reference",
"against_income_account",
"pos_total_qty"
"against_income_account"
],
"fields": [
{
@ -265,14 +261,6 @@
"options": "POS Profile",
"print_hide": 1
},
{
"fieldname": "offline_pos_name",
"fieldtype": "Data",
"hidden": 1,
"label": "Offline POS Name",
"print_hide": 1,
"read_only": 1
},
{
"allow_on_submit": 1,
"default": "0",
@ -348,26 +336,16 @@
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "return_against",
"fieldname": "returns",
"fieldtype": "Section Break",
"label": "Returns"
},
{
"depends_on": "return_against",
"fieldname": "return_against",
"fieldtype": "Link",
"label": "Return Against POS Invoice",
"label": "Return Against",
"no_copy": 1,
"options": "POS Invoice",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "eval: doc.is_return && doc.return_against",
@ -587,19 +565,21 @@
},
{
"fieldname": "sec_warehouse",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Warehouse"
},
{
"depends_on": "update_stock",
"fieldname": "set_warehouse",
"fieldtype": "Link",
"label": "Set Source Warehouse",
"label": "Source Warehouse",
"options": "Warehouse",
"print_hide": 1
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"label": "Items",
"oldfieldtype": "Section Break",
"options": "fa fa-shopping-cart"
},
@ -1501,7 +1481,7 @@
"allow_on_submit": 1,
"fieldname": "sales_team",
"fieldtype": "Table",
"label": "Sales Team1",
"label": "Sales Team",
"oldfieldname": "sales_team",
"oldfieldtype": "Table",
"options": "Sales Team",
@ -1560,15 +1540,6 @@
"print_hide": 1,
"report_hide": 1
},
{
"fieldname": "pos_total_qty",
"fieldtype": "Float",
"hidden": 1,
"label": "Total Qty",
"print_hide": 1,
"print_hide_if_no_value": 1,
"read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "consolidated_invoice",
@ -1581,7 +1552,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2020-10-30 13:56:51.056083",
"modified": "2021-02-01 15:03:33.800707",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
@ -1626,7 +1597,6 @@
"role": "All"
}
],
"quick_entry": 1,
"search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount",
"show_name_in_global_search": 1,
"sort_field": "modified",

View File

@ -6,10 +6,9 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from erpnext.controllers.selling_controller import SellingController
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
from erpnext.accounts.utils import get_account_currency
from erpnext.accounts.party import get_party_account, get_due_date
from frappe.utils import cint, flt, getdate, nowdate, get_link_to_form
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
@ -58,6 +57,22 @@ class POSInvoice(SalesInvoice):
self.apply_loyalty_points()
self.check_phone_payments()
self.set_status(update=True)
def before_cancel(self):
if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
pos_closing_entry = frappe.get_all(
"POS Invoice Reference",
ignore_permissions=True,
filters={ 'pos_invoice': self.name },
pluck="parent",
limit=1
)
frappe.throw(
_('You need to cancel POS Closing Entry {} to be able to cancel this document.').format(
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
),
title=_('Not Allowed')
)
def on_cancel(self):
# run on cancel method of selling controller
@ -78,7 +93,7 @@ class POSInvoice(SalesInvoice):
mode_of_payment=pay.mode_of_payment, status="Paid"),
fieldname="grand_total")
if pay.amount != paid_amt:
if paid_amt and pay.amount != paid_amt:
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
def validate_stock_availablility(self):
@ -297,7 +312,9 @@ class POSInvoice(SalesInvoice):
self.set(fieldname, profile.get(fieldname))
if self.customer:
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
customer_price_list, customer_group, customer_currency = frappe.db.get_value(
"Customer", self.customer, ['default_price_list', 'customer_group', 'default_currency']
)
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
else:
@ -305,6 +322,8 @@ class POSInvoice(SalesInvoice):
if selling_price_list:
self.set('selling_price_list', selling_price_list)
if customer_currency != profile.get('currency'):
self.set('currency', customer_currency)
# set pos values in items
for item in self.get("items"):

View File

@ -290,7 +290,7 @@ class TestPOSInvoice(unittest.TestCase):
def test_merging_into_sales_invoice_with_discount(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
@ -306,7 +306,7 @@ class TestPOSInvoice(unittest.TestCase):
})
pos_inv2.submit()
merge_pos_invoices()
consolidate_pos_invoices()
pos_inv.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
@ -315,7 +315,7 @@ class TestPOSInvoice(unittest.TestCase):
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
@ -348,7 +348,7 @@ class TestPOSInvoice(unittest.TestCase):
})
pos_inv2.submit()
merge_pos_invoices()
consolidate_pos_invoices()
pos_inv.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
@ -357,7 +357,7 @@ class TestPOSInvoice(unittest.TestCase):
def test_merging_with_validate_selling_price(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
@ -393,7 +393,7 @@ class TestPOSInvoice(unittest.TestCase):
})
pos_inv2.submit()
merge_pos_invoices()
consolidate_pos_invoices()
pos_inv2.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")

View File

@ -7,6 +7,8 @@
"field_order": [
"posting_date",
"customer",
"column_break_3",
"pos_closing_entry",
"section_break_3",
"pos_invoices",
"references_section",
@ -76,11 +78,22 @@
"label": "Consolidated Credit Note",
"options": "Sales Invoice",
"read_only": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "pos_closing_entry",
"fieldtype": "Link",
"label": "POS Closing Entry",
"options": "POS Closing Entry"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-05-29 15:08:41.317100",
"modified": "2020-12-01 11:53:57.267579",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Merge Log",

View File

@ -5,10 +5,13 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
from frappe.model.document import Document
from frappe.model.mapper import map_doc
from frappe.model import default_fields
from frappe.model.document import Document
from frappe.utils import flt, getdate, nowdate
from frappe.utils.background_jobs import enqueue
from frappe.model.mapper import map_doc, map_child_doc
from frappe.utils.scheduler import is_scheduler_inactive
from frappe.core.page.background_jobs.background_jobs import get_info
from six import iteritems
@ -61,7 +64,13 @@ class POSInvoiceMergeLog(Document):
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
self.update_pos_invoices(sales_invoice, credit_note)
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
def on_cancel(self):
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
self.update_pos_invoices(pos_invoice_docs)
self.cancel_linked_invoices()
def process_merging_into_sales_invoice(self, data):
sales_invoice = self.get_new_sales_invoice()
@ -83,7 +92,7 @@ class POSInvoiceMergeLog(Document):
credit_note.is_consolidated = 1
# TODO: return could be against multiple sales invoice which could also have been consolidated?
credit_note.return_against = self.consolidated_invoice
# credit_note.return_against = self.consolidated_invoice
credit_note.save()
credit_note.submit()
self.consolidated_credit_note = credit_note.name
@ -111,7 +120,9 @@ class POSInvoiceMergeLog(Document):
i.qty = i.qty + item.qty
if not found:
item.rate = item.net_rate
items.append(item)
item.price_list_rate = 0
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
items.append(si_item)
for tax in doc.get('taxes'):
found = False
@ -147,6 +158,8 @@ class POSInvoiceMergeLog(Document):
invoice.set('taxes', taxes)
invoice.additional_discount_percentage = 0
invoice.discount_amount = 0.0
invoice.taxes_and_charges = None
invoice.ignore_pricing_rule = 1
return invoice
@ -159,17 +172,21 @@ class POSInvoiceMergeLog(Document):
return sales_invoice
def update_pos_invoices(self, sales_invoice, credit_note):
for d in self.pos_invoices:
doc = frappe.get_doc('POS Invoice', d.pos_invoice)
if not doc.is_return:
doc.update({'consolidated_invoice': sales_invoice})
else:
doc.update({'consolidated_invoice': credit_note})
def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
for doc in invoice_docs:
doc.load_from_db()
doc.update({ 'consolidated_invoice': None if self.docstatus==2 else (credit_note if doc.is_return else sales_invoice) })
doc.set_status(update=True)
doc.save()
def get_all_invoices():
def cancel_linked_invoices(self):
for si_name in [self.consolidated_invoice, self.consolidated_credit_note]:
if not si_name: continue
si = frappe.get_doc('Sales Invoice', si_name)
si.flags.ignore_validate = True
si.cancel()
def get_all_unconsolidated_invoices():
filters = {
'consolidated_invoice': [ 'in', [ '', None ]],
'status': ['not in', ['Consolidated']],
@ -180,7 +197,7 @@ def get_all_invoices():
return pos_invoices
def get_invoices_customer_map(pos_invoices):
def get_invoice_customer_map(pos_invoices):
# pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] }
pos_invoice_customer_map = {}
for invoice in pos_invoices:
@ -190,20 +207,82 @@ def get_invoices_customer_map(pos_invoices):
return pos_invoice_customer_map
def merge_pos_invoices(pos_invoices=[]):
if not pos_invoices:
pos_invoices = get_all_invoices()
pos_invoice_map = get_invoices_customer_map(pos_invoices)
create_merge_logs(pos_invoice_map)
def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices()
invoice_by_customer = get_invoice_customer_map(invoices)
def create_merge_logs(pos_invoice_customer_map):
for customer, invoices in iteritems(pos_invoice_customer_map):
if len(invoices) >= 5 and closing_entry:
enqueue_job(create_merge_logs, invoice_by_customer, closing_entry)
closing_entry.set_status(update=True, status='Queued')
else:
create_merge_logs(invoice_by_customer, closing_entry)
def unconsolidate_pos_invoices(closing_entry):
merge_logs = frappe.get_all(
'POS Invoice Merge Log',
filters={ 'pos_closing_entry': closing_entry.name },
pluck='name'
)
if len(merge_logs) >= 5:
enqueue_job(cancel_merge_logs, merge_logs, closing_entry)
closing_entry.set_status(update=True, status='Queued')
else:
cancel_merge_logs(merge_logs, closing_entry)
def create_merge_logs(invoice_by_customer, closing_entry={}):
for customer, invoices in iteritems(invoice_by_customer):
merge_log = frappe.new_doc('POS Invoice Merge Log')
merge_log.posting_date = getdate(nowdate())
merge_log.customer = customer
merge_log.pos_closing_entry = closing_entry.get('name', None)
merge_log.set('pos_invoices', invoices)
merge_log.save(ignore_permissions=True)
merge_log.submit()
if closing_entry:
closing_entry.set_status(update=True, status='Submitted')
closing_entry.update_opening_entry()
def cancel_merge_logs(merge_logs, closing_entry={}):
for log in merge_logs:
merge_log = frappe.get_doc('POS Invoice Merge Log', log)
merge_log.flags.ignore_permissions = True
merge_log.cancel()
if closing_entry:
closing_entry.set_status(update=True, status='Cancelled')
closing_entry.update_opening_entry(for_cancel=True)
def enqueue_job(job, invoice_by_customer, closing_entry):
check_scheduler_status()
job_name = closing_entry.get("name")
if not job_already_enqueued(job_name):
enqueue(
job,
queue="long",
timeout=10000,
event="processing_merge_logs",
job_name=job_name,
closing_entry=closing_entry,
invoice_by_customer=invoice_by_customer,
now=frappe.conf.developer_mode or frappe.flags.in_test
)
if job == create_merge_logs:
msg = _('POS Invoices will be consolidated in a background process')
else:
msg = _('POS Invoices will be unconsolidated in a background process')
frappe.msgprint(msg, alert=1)
def check_scheduler_status():
if is_scheduler_inactive() and not frappe.flags.in_test:
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
def job_already_enqueued(job_name):
enqueued_jobs = [d.get("job_name") for d in get_info()]
if job_name in enqueued_jobs:
return True

View File

@ -7,7 +7,7 @@ import frappe
import unittest
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
class TestPOSInvoiceMergeLog(unittest.TestCase):
@ -34,7 +34,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
})
pos_inv3.submit()
merge_pos_invoices()
consolidate_pos_invoices()
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
@ -79,7 +79,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
pos_inv_cn.paid_amount = -300
pos_inv_cn.submit()
merge_pos_invoices()
consolidate_pos_invoices()
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))

View File

@ -6,7 +6,6 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cint, get_link_to_form
from frappe.model.document import Document
from erpnext.controllers.status_updater import StatusUpdater
class POSOpeningEntry(StatusUpdater):

View File

@ -5,7 +5,7 @@
frappe.listview_settings['POS Opening Entry'] = {
get_indicator: function(doc) {
var status_color = {
"Draft": "grey",
"Draft": "red",
"Open": "orange",
"Closed": "green",
"Cancelled": "red"

View File

@ -57,6 +57,8 @@ frappe.ui.form.on('POS Profile', {
}
};
});
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
refresh: function(frm) {
@ -67,6 +69,7 @@ frappe.ui.form.on('POS Profile', {
company: function(frm) {
frm.trigger("toggle_display_account_head");
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
toggle_display_account_head: function(frm) {

View File

@ -6,14 +6,11 @@
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"disabled",
"section_break_2",
"customer",
"company",
"customer",
"country",
"disabled",
"column_break_9",
"update_stock",
"ignore_pricing_rule",
"warehouse",
"campaign",
"company_address",
@ -25,8 +22,14 @@
"hide_images",
"hide_unavailable_items",
"auto_add_item_to_cart",
"item_groups",
"column_break_16",
"update_stock",
"ignore_pricing_rule",
"allow_rate_change",
"allow_discount_change",
"section_break_23",
"item_groups",
"column_break_25",
"customer_groups",
"section_break_16",
"print_format",
@ -57,10 +60,6 @@
"fieldtype": "Check",
"label": "Disabled"
},
{
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"fieldname": "customer",
"fieldtype": "Link",
@ -309,6 +308,7 @@
"default": "1",
"fieldname": "update_stock",
"fieldtype": "Check",
"hidden": 1,
"label": "Update Stock",
"read_only": 1
},
@ -329,13 +329,55 @@
"fieldname": "auto_add_item_to_cart",
"fieldtype": "Check",
"label": "Automatically Add Filtered Item To Cart"
},
{
"default": "0",
"fieldname": "allow_rate_change",
"fieldtype": "Check",
"label": "Allow User to Edit Rate"
},
{
"default": "0",
"fieldname": "allow_discount_change",
"fieldtype": "Check",
"label": "Allow User to Edit Discount"
},
{
"fieldname": "section_break_23",
"fieldtype": "Section Break",
"label": "Filters"
},
{
"fieldname": "column_break_25",
"fieldtype": "Column Break"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-12-10 13:59:28.877572",
"links": [
{
"group": "Invoices",
"link_doctype": "Sales Invoice",
"link_fieldname": "pos_profile"
},
{
"group": "Invoices",
"link_doctype": "POS Invoice",
"link_fieldname": "pos_profile"
},
{
"group": "Opening & Closing",
"link_doctype": "POS Opening Entry",
"link_fieldname": "pos_profile"
},
{
"group": "Opening & Closing",
"link_doctype": "POS Closing Entry",
"link_fieldname": "pos_profile"
}
],
"modified": "2021-02-01 13:52:51.081311",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@ -1,14 +0,0 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'pos_profile',
'transactions': [
{
'items': ['Sales Invoice', 'POS Closing Entry', 'POS Opening Entry']
}
]
}

View File

@ -56,6 +56,7 @@ class TestPricingRule(unittest.TestCase):
self.assertEqual(details.get("discount_percentage"), 10)
prule = frappe.get_doc(test_record.copy())
prule.priority = 1
prule.applicable_for = "Customer"
prule.title = "_Test Pricing Rule for Customer"
self.assertRaises(MandatoryError, prule.insert)
@ -261,6 +262,7 @@ class TestPricingRule(unittest.TestCase):
"rate_or_discount": "Discount Percentage",
"rate": 0,
"discount_percentage": 17.5,
"priority": 1,
"company": "_Test Company"
}).insert()
@ -557,6 +559,7 @@ def make_pricing_rule(**args):
"rate": args.rate or 0.0,
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
"condition": args.condition or '',
"priority": 1,
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
})

View File

@ -41,10 +41,11 @@ def get_pricing_rules(args, doc=None):
if not pricing_rules: return []
if apply_multiple_pricing_rules(pricing_rules):
pricing_rules = sorted_by_priority(pricing_rules)
pricing_rules = sorted_by_priority(pricing_rules, args, doc)
for pricing_rule in pricing_rules:
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
if pricing_rule:
if isinstance(pricing_rule, list):
rules.extend(pricing_rule)
else:
rules.append(pricing_rule)
else:
pricing_rule = filter_pricing_rules(args, pricing_rules, doc)
@ -53,17 +54,22 @@ def get_pricing_rules(args, doc=None):
return rules
def sorted_by_priority(pricing_rules):
def sorted_by_priority(pricing_rules, args, doc=None):
# If more than one pricing rules, then sort by priority
pricing_rules_list = []
pricing_rule_dict = {}
for pricing_rule in pricing_rules:
if not pricing_rule.get("priority"): continue
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
for pricing_rule in pricing_rules:
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
if pricing_rule:
if not pricing_rule.get('priority'):
pricing_rule['priority'] = 1
if pricing_rule.get('apply_multiple_pricing_rules'):
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
for key in sorted(pricing_rule_dict):
pricing_rules_list.append(pricing_rule_dict.get(key))
pricing_rules_list.extend(pricing_rule_dict.get(key))
return pricing_rules_list or pricing_rules
@ -144,9 +150,7 @@ def apply_multiple_pricing_rules(pricing_rules):
if not apply_multiple_rule: return False
if (apply_multiple_rule
and len(apply_multiple_rule) == len(pricing_rules)):
return True
return True
def _get_tree_conditions(args, parenttype, table, allow_blank=True):
field = frappe.scrub(parenttype)
@ -264,18 +268,6 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
if max_priority:
pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
# apply internal priority
all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory",
"supplier", "supplier_group", "campaign", "sales_partner", "variant_of"]
if len(pricing_rules) > 1:
for field_set in [["item_code", "variant_of", "item_group", "brand"],
["customer", "customer_group", "territory"], ["supplier", "supplier_group"]]:
remaining_fields = list(set(all_fields) - set(field_set))
if if_all_rules_same(pricing_rules, remaining_fields):
pricing_rules = apply_internal_priority(pricing_rules, field_set, args)
break
if pricing_rules and not isinstance(pricing_rules, list):
pricing_rules = list(pricing_rules)

View File

@ -26,6 +26,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
};
});
},
company: function() {
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
},
onload: function() {
this._super();
@ -41,6 +46,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
this.frm.trigger('supplier');
}
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
},
refresh: function(doc) {
@ -268,8 +275,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
supplier: function() {
var me = this;
if(this.frm.updating_party_details)
// Do not update if inter company reference is there as the details will already be updated
if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference)
return;
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
{
posting_date: this.frm.doc.posting_date,
@ -498,7 +508,7 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
frappe.ui.form.on("Purchase Invoice", {
setup: function(frm) {
frm.custom_make_buttons = {
'Purchase Invoice': 'Debit Note',
'Purchase Invoice': 'Return / Debit Note',
'Payment Entry': 'Payment'
}
@ -511,15 +521,6 @@ frappe.ui.form.on("Purchase Invoice", {
}
}
}
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0
}
};
});
},
onload: function(frm) {

View File

@ -57,8 +57,8 @@
"set_warehouse",
"rejected_warehouse",
"col_break_warehouse",
"set_from_warehouse",
"is_subcontracted",
"supplier_warehouse",
"items_section",
"update_stock",
"scan_barcode",
@ -515,6 +515,7 @@
},
{
"depends_on": "update_stock",
"description": "Sets 'Accepted Warehouse' in each row of the items table.",
"fieldname": "set_warehouse",
"fieldtype": "Link",
"label": "Set Accepted Warehouse",
@ -543,17 +544,6 @@
"options": "No\nYes",
"print_hide": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
"no_copy": 1,
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
"width": "50px"
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
@ -1232,7 +1222,9 @@
"fieldname": "inter_company_invoice_reference",
"fieldtype": "Link",
"label": "Inter Company Invoice Reference",
"no_copy": 1,
"options": "Sales Invoice",
"print_hide": 1,
"read_only": 1
},
{
@ -1356,13 +1348,25 @@
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company"
},
{
"depends_on": "eval:doc.update_stock && (doc.is_subcontracted==\"Yes\" || doc.is_internal_supplier)",
"description": "Sets 'From Warehouse' in each row of the items table.",
"fieldname": "set_from_warehouse",
"fieldtype": "Link",
"label": "Set From Warehouse",
"no_copy": 1,
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
"width": "50px"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2020-12-11 12:46:12.796378",
"modified": "2020-12-26 20:49:03.305063",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -443,7 +443,7 @@ class PurchaseInvoice(BuyingController):
else:
self.stock_received_but_not_billed = None
self.expenses_included_in_valuation = None
self.negative_expense_to_be_booked = 0.0
gl_entries = []
@ -457,7 +457,7 @@ class PurchaseInvoice(BuyingController):
self.make_internal_transfer_gl_entries(gl_entries)
gl_entries = make_regional_gl_entries(gl_entries, self)
gl_entries = merge_similar_entries(gl_entries)
self.make_payment_gl_entries(gl_entries)
@ -480,7 +480,7 @@ class PurchaseInvoice(BuyingController):
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle
# Did not use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total"))
gl_entries.append(
@ -511,8 +511,8 @@ class PurchaseInvoice(BuyingController):
voucher_wise_stock_value = {}
if self.update_stock:
for d in frappe.get_all('Stock Ledger Entry',
fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}):
voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference)
fields = ["voucher_detail_no", "stock_value_difference", "warehouse"], filters={'voucher_no': self.name}):
voucher_wise_stock_value.setdefault((d.voucher_detail_no, d.warehouse), d.stock_value_difference)
valuation_tax_accounts = [d.account_head for d in self.get("taxes")
if d.category in ('Valuation', 'Total and Valuation')
@ -563,16 +563,17 @@ class PurchaseInvoice(BuyingController):
)
else:
gl_entries.append(
self.get_gl_dict({
"account": item.expense_account,
"against": self.supplier,
"debit": warehouse_debit_amount,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item)
)
if not self.is_internal_transfer():
gl_entries.append(
self.get_gl_dict({
"account": item.expense_account,
"against": self.supplier,
"debit": warehouse_debit_amount,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item)
)
# Amount added through landed-cost-voucher
if landed_cost_entries:
@ -582,7 +583,8 @@ class PurchaseInvoice(BuyingController):
"against": item.expense_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(amount),
"credit": flt(amount["base_amount"]),
"credit_in_account_currency": flt(amount["amount"]),
"project": item.project or self.project
}, item=item))
@ -624,13 +626,14 @@ class PurchaseInvoice(BuyingController):
if expense_booked_in_pr:
expense_account = service_received_but_not_billed_account
gl_entries.append(self.get_gl_dict({
"account": expense_account,
"against": self.supplier,
"debit": amount,
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item))
if not self.is_internal_transfer():
gl_entries.append(self.get_gl_dict({
"account": expense_account,
"against": self.supplier,
"debit": amount,
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item))
# If asset is bought through this document and not linked to PR
if self.update_stock and item.landed_cost_voucher_amount:
@ -795,10 +798,10 @@ class PurchaseInvoice(BuyingController):
# Stock ledger value is not matching with the warehouse amount
if (self.update_stock and voucher_wise_stock_value.get(item.name) and
warehouse_debit_amount != flt(voucher_wise_stock_value.get(item.name), net_amt_precision)):
warehouse_debit_amount != flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)):
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_amount = flt(voucher_wise_stock_value.get(item.name), net_amt_precision)
stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
stock_adjustment_amt = warehouse_debit_amount - stock_amount
gl_entries.append(
@ -999,10 +1002,10 @@ class PurchaseInvoice(BuyingController):
self.delete_auto_created_batches()
self.make_gl_entries_on_cancel()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
self.update_project()
frappe.db.set(self, 'status', 'Cancelled')

View File

@ -19,7 +19,7 @@ frappe.listview_settings['Purchase Invoice'] = {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
}
} else if (cint(doc.is_return)) {
return [__("Return"), "darkgrey", "is_return,=,Yes"];
return [__("Return"), "gray", "is_return,=,Yes"];
} else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"];
} else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {

View File

@ -426,26 +426,31 @@ class TestPurchaseInvoice(unittest.TestCase):
)
def test_total_purchase_cost_for_project(self):
make_project({'project_name':'_Test Project'})
if not frappe.db.exists("Project", {"project_name": "_Test Project for Purchase"}):
project = make_project({'project_name':'_Test Project for Purchase'})
else:
project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"})
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""")
from `tabPurchase Invoice Item`
where project = '{0}'
and docstatus=1""".format(project.name))
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project="_Test Project")
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name)
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
existing_purchase_cost + 15000)
pi1 = make_purchase_invoice(qty=10, project="_Test Project")
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
pi1 = make_purchase_invoice(qty=10, project=project.name)
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
existing_purchase_cost + 15500)
pi1.cancel()
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
existing_purchase_cost + 15000)
pi.cancel()
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"), existing_purchase_cost)
def test_return_purchase_invoice_with_perpetual_inventory(self):
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
@ -860,17 +865,17 @@ class TestPurchaseInvoice(unittest.TestCase):
})
pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
pi.items[0].project = item_project.project_name
pi.project = project.project_name
pi.items[0].project = item_project.name
pi.project = project.name
pi.submit()
expected_values = {
"Creditors - _TC": {
"project": project.project_name
"project": project.name
},
"_Test Account Cost for Goods Sold - _TC": {
"project": item_project.project_name
"project": item_project.name
}
}

View File

@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "hash",
"creation": "2013-05-22 12:43:10",
"doctype": "DocType",
@ -87,6 +88,7 @@
"po_detail",
"purchase_receipt",
"pr_detail",
"sales_invoice_item",
"item_weight_details",
"weight_per_unit",
"total_weight",
@ -553,8 +555,8 @@
"fieldtype": "Link",
"hidden": 1,
"label": "Brand",
"print_hide": 1,
"options": "Brand"
"options": "Brand",
"print_hide": 1
},
{
"fetch_from": "item_code.item_group",
@ -562,9 +564,9 @@
"fieldname": "item_group",
"fieldtype": "Link",
"label": "Item Group",
"options": "Item Group",
"print_hide": 1,
"read_only": 1,
"options": "Item Group"
"read_only": 1
},
{
"description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges",
@ -759,10 +761,11 @@
"read_only": 1
},
{
"depends_on": "eval:parent.is_internal_supplier && parent.update_stock",
"fieldname": "from_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Supplier Warehouse",
"label": "From Warehouse",
"options": "Warehouse"
},
{
@ -779,11 +782,20 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "sales_invoice_item",
"fieldtype": "Data",
"label": "Sales Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"modified": "2020-08-20 11:48:01.398356",
"links": [],
"modified": "2020-12-26 17:20:36.415791",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
@ -791,4 +803,4 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}
}

View File

@ -6,8 +6,5 @@ import frappe
from frappe.model.document import Document
from erpnext.controllers.print_settings import print_settings_for_item_table
class PurchaseInvoiceItem(Document):
def __setup__(self):
print_settings_for_item_table(self)
pass

View File

@ -5,18 +5,22 @@
cur_frm.pformat.print_heading = 'Invoice';
{% include 'erpnext/selling/sales_common.js' %};
frappe.provide("erpnext.accounts");
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
setup: function(doc) {
this.setup_posting_date_time_check();
this._super(doc);
},
company: function() {
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
},
onload: function() {
var me = this;
this._super();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice'];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
this.frm.set_df_property("debit_to", "print_hide", 0);
@ -33,6 +37,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
me.frm.refresh_fields();
}
erpnext.queries.setup_warehouse_query(this.frm);
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
},
refresh: function(doc, dt, dn) {
@ -126,16 +131,15 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.set_default_print_format();
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
frappe.model.with_doc("Customer", me.frm.doc.customer, function() {
var customer = frappe.model.get_doc("Customer", me.frm.doc.customer);
var internal = customer.is_internal_customer;
var disabled = customer.disabled;
if (internal == 1 && disabled == 0) {
me.frm.add_custom_button("Inter Company Invoice", function() {
me.make_inter_company_invoice();
}, __('Create'));
}
});
let internal = me.frm.doc.is_internal_customer;
if (internal) {
let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Invoice" :
"Inter Company Purchase Invoice";
me.frm.add_custom_button(button_label, function() {
me.make_inter_company_invoice();
}, __('Create'));
}
}
},
@ -571,15 +575,6 @@ frappe.ui.form.on('Sales Invoice', {
};
});
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0
}
};
});
frm.set_query("unrealized_profit_loss_account", function() {
return {
filters: {
@ -592,7 +587,7 @@ frappe.ui.form.on('Sales Invoice', {
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Sales Return',
'Sales Invoice': 'Return / Credit Note',
'Payment Request': 'Payment Request',
'Payment Entry': 'Payment'
},
@ -674,12 +669,12 @@ frappe.ui.form.on('Sales Invoice', {
};
},
// When multiple companies are set up. in case company name is changed set default company address
company:function(frm){
if (frm.doc.company)
{
company: function(frm){
if (frm.doc.company) {
frappe.call({
method:"erpnext.setup.doctype.company.company.get_default_company_address",
args:{name:frm.doc.company, existing_address: frm.doc.company_address},
method: "erpnext.setup.doctype.company.company.get_default_company_address",
args: {name:frm.doc.company, existing_address: frm.doc.company_address || ""},
debounce: 2000,
callback: function(r){
if (r.message){
frm.set_value("company_address",r.message)

View File

@ -12,11 +12,11 @@
"customer",
"customer_name",
"tax_id",
"pos_profile",
"is_pos",
"is_consolidated",
"pos_profile",
"offline_pos_name",
"is_return",
"update_billed_amount_in_sales_order",
"column_break1",
"company",
"company_tax_id",
@ -24,11 +24,8 @@
"posting_time",
"set_posting_time",
"due_date",
"amended_from",
"returns",
"return_against",
"column_break_21",
"update_billed_amount_in_sales_order",
"amended_from",
"accounting_dimensions_section",
"project",
"dimension_col_break",
@ -60,6 +57,8 @@
"ignore_pricing_rule",
"sec_warehouse",
"set_warehouse",
"column_break_55",
"set_target_warehouse",
"items_section",
"update_stock",
"scan_barcode",
@ -186,8 +185,7 @@
"column_break_140",
"auto_repeat",
"update_auto_repeat_reference",
"against_income_account",
"pos_total_qty"
"against_income_account"
],
"fields": [
{
@ -294,16 +292,6 @@
"options": "POS Profile",
"print_hide": 1
},
{
"fieldname": "offline_pos_name",
"fieldtype": "Data",
"hidden": 1,
"hide_days": 1,
"hide_seconds": 1,
"label": "Offline POS Name",
"print_hide": 1,
"read_only": 1
},
{
"default": "0",
"fieldname": "is_return",
@ -402,33 +390,19 @@
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "return_against",
"fieldname": "returns",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Returns"
},
{
"depends_on": "return_against",
"fieldname": "return_against",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Return Against Sales Invoice",
"label": "Return Against",
"no_copy": 1,
"options": "Sales Invoice",
"print_hide": 1,
"read_only": 1,
"search_index": 1
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break",
"hide_days": 1,
"hide_seconds": 1
},
{
"default": "0",
"depends_on": "eval: doc.is_return && doc.return_against",
@ -676,7 +650,8 @@
"fieldname": "sec_warehouse",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1
"hide_seconds": 1,
"label": "Warehouse"
},
{
"depends_on": "update_stock",
@ -684,7 +659,7 @@
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Set Source Warehouse",
"label": "Source Warehouse",
"options": "Warehouse",
"print_hide": 1
},
@ -693,6 +668,7 @@
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Items",
"oldfieldtype": "Section Break",
"options": "fa fa-shopping-cart"
},
@ -1902,17 +1878,6 @@
"print_hide": 1,
"report_hide": 1
},
{
"fieldname": "pos_total_qty",
"fieldtype": "Float",
"hidden": 1,
"hide_days": 1,
"hide_seconds": 1,
"label": "Total Qty",
"print_hide": 1,
"print_hide_if_no_value": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
@ -1969,13 +1934,31 @@
"label": "Represents Company",
"options": "Company",
"read_only": 1
},
{
"fieldname": "column_break_55",
"fieldtype": "Column Break"
},
{
"depends_on": "eval: doc.is_internal_customer && doc.update_stock",
"fieldname": "set_target_warehouse",
"fieldtype": "Link",
"label": "Set Target Warehouse",
"options": "Warehouse"
}
],
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
"links": [],
"modified": "2020-12-11 12:48:31.769958",
"links": [
{
"custom": 1,
"group": "Reference",
"link_doctype": "POS Invoice",
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2021-02-01 15:42:26.261540",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -4,9 +4,9 @@
from __future__ import unicode_literals
import frappe, erpnext
import frappe.defaults
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form
from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.accounts.party import get_party_account, get_due_date, get_party_details
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.selling_controller import SellingController
from erpnext.accounts.utils import get_account_currency
@ -21,6 +21,8 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from frappe.model.utils import get_fetch_values
from frappe.contacts.doctype.address.address import get_address_display
from erpnext.healthcare.utils import manage_invoice_submit_cancel
@ -53,7 +55,7 @@ class SalesInvoice(SellingController):
"""Set indicator for portal"""
if self.outstanding_amount < 0:
self.indicator_title = _("Credit Note Issued")
self.indicator_color = "darkgrey"
self.indicator_color = "gray"
elif self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()):
self.indicator_color = "orange"
self.indicator_title = _("Unpaid")
@ -62,7 +64,7 @@ class SalesInvoice(SellingController):
self.indicator_title = _("Overdue")
elif cint(self.is_return) == 1:
self.indicator_title = _("Return")
self.indicator_color = "darkgrey"
self.indicator_color = "gray"
else:
self.indicator_color = "green"
self.indicator_title = _("Paid")
@ -179,7 +181,7 @@ class SalesInvoice(SellingController):
# this sequence because outstanding may get -ve
self.make_gl_entries()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
@ -231,7 +233,25 @@ class SalesInvoice(SellingController):
if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice."))
def check_if_consolidated_invoice(self):
# since POS Invoice extends Sales Invoice, we explicitly check if doctype is Sales Invoice
if self.doctype == "Sales Invoice" and self.is_consolidated:
invoice_or_credit_note = "consolidated_credit_note" if self.is_return else "consolidated_invoice"
pos_closing_entry = frappe.get_all(
"POS Invoice Merge Log",
filters={ invoice_or_credit_note: self.name },
pluck="pos_closing_entry"
)
if pos_closing_entry:
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format(
frappe.bold("Consolidated Sales Invoice"),
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
)
frappe.throw(msg, title=_("Not Allowed"))
def before_cancel(self):
self.check_if_consolidated_invoice()
super(SalesInvoice, self).before_cancel()
self.update_time_sheet(None)
@ -261,10 +281,10 @@ class SalesInvoice(SellingController):
self.update_stock_ledger()
self.make_gl_entries_on_cancel()
if self.update_stock == 1:
self.repost_future_sle_and_gle()
frappe.db.set(self, 'status', 'Cancelled')
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
@ -433,7 +453,9 @@ class SalesInvoice(SellingController):
if not for_validate and not self.customer:
self.customer = pos.customer
self.ignore_pricing_rule = pos.ignore_pricing_rule
if not for_validate:
self.ignore_pricing_rule = pos.ignore_pricing_rule
if pos.get('account_for_change_amount'):
self.account_for_change_amount = pos.get('account_for_change_amount')
@ -549,7 +571,12 @@ class SalesInvoice(SellingController):
self.against_income_account = ','.join(against_acc)
def add_remarks(self):
if not self.remarks: self.remarks = 'No Remarks'
if not self.remarks:
if self.po_no and self.po_date:
self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no,
formatdate(self.po_date))
else:
self.remarks = _("No Remarks")
def validate_auto_set_posting_time(self):
# Don't auto set the posting date and time if invoice is amended
@ -1529,7 +1556,7 @@ def validate_inter_company_transaction(doc, doctype):
details = get_inter_company_details(doc, doctype)
price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"] else doc.buying_price_list
valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1})
if not valid_price_list:
if not valid_price_list and not doc.is_internal_transfer():
frappe.throw(_("Selected Price List should have buying and selling fields checked."))
party = details.get("party")
@ -1552,6 +1579,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if doctype in ["Sales Invoice", "Sales Order"]:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
source_document_warehouse_field = 'target_warehouse'
target_document_warehouse_field = 'from_warehouse'
else:
@ -1565,6 +1593,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
def set_missing_values(source, target):
target.run_method("set_missing_values")
set_purchase_references(target)
def update_details(source_doc, target_doc, source_parent):
target_doc.inter_company_invoice_reference = source_doc.name
@ -1572,19 +1601,38 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
currency = frappe.db.get_value('Supplier', details.get('party'), 'default_currency')
target_doc.company = details.get("company")
target_doc.supplier = details.get("party")
target_doc.is_internal_supplier = 1
target_doc.ignore_pricing_rule = 1
target_doc.buying_price_list = source_doc.selling_price_list
# Invert Addresses
update_address(target_doc, 'supplier_address', 'address_display', source_doc.company_address)
update_address(target_doc, 'shipping_address', 'shipping_address_display', source_doc.customer_address)
if currency:
target_doc.currency = currency
update_taxes(target_doc, party=target_doc.supplier, party_type='Supplier', company=target_doc.company,
doctype=target_doc.doctype, party_address=target_doc.supplier_address,
company_address=target_doc.shipping_address)
else:
currency = frappe.db.get_value('Customer', details.get('party'), 'default_currency')
target_doc.company = details.get("company")
target_doc.customer = details.get("party")
target_doc.selling_price_list = source_doc.buying_price_list
update_address(target_doc, 'company_address', 'company_address_display', source_doc.supplier_address)
update_address(target_doc, 'shipping_address_name', 'shipping_address', source_doc.shipping_address)
update_address(target_doc, 'customer_address', 'address_display', source_doc.shipping_address)
if currency:
target_doc.currency = currency
update_taxes(target_doc, party=target_doc.customer, party_type='Customer', company=target_doc.company,
doctype=target_doc.doctype, party_address=target_doc.customer_address,
company_address=target_doc.company_address, shipping_address_name=target_doc.shipping_address_name)
item_field_map = {
"doctype": target_doctype + " Item",
"field_no_map": [
@ -1592,25 +1640,33 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
"expense_account",
"cost_center",
"warehouse"
]
],
"field_map": {
'rate': 'rate',
}
}
if source_doc.get('update_stock'):
item_field_map.update({
'field_map': {
source_document_warehouse_field: target_document_warehouse_field,
'batch_no': 'batch_no',
'serial_no': 'serial_no'
}
if doctype in ["Sales Invoice", "Sales Order"]:
item_field_map["field_map"].update({
"name": target_detail_field,
})
if source_doc.get('update_stock'):
item_field_map["field_map"].update({
source_document_warehouse_field: target_document_warehouse_field,
'batch_no': 'batch_no',
'serial_no': 'serial_no'
})
doclist = get_mapped_doc(doctype, source_name, {
doctype: {
"doctype": target_doctype,
"postprocess": update_details,
"set_target_warehouse": "set_from_warehouse",
"field_no_map": [
"taxes_and_charges"
"taxes_and_charges",
"set_warehouse",
"shipping_address"
]
},
doctype +" Item": item_field_map
@ -1619,6 +1675,110 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
return doclist
def set_purchase_references(doc):
# add internal PO or PR links if any
if doc.is_internal_transfer():
if doc.doctype == 'Purchase Receipt':
so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference)
if so_item_map:
pd_item_map, parent_child_map, warehouse_map = \
get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
update_pr_items(doc, so_item_map, pd_item_map, parent_child_map, warehouse_map)
elif doc.doctype == 'Purchase Invoice':
dn_item_map, so_item_map = get_sales_invoice_details(doc.inter_company_invoice_reference)
# First check for Purchase receipt
if list(dn_item_map.values()):
pd_item_map, parent_child_map, warehouse_map = \
get_pd_details('Purchase Receipt Item', dn_item_map, 'delivery_note_item')
update_pi_items(doc, 'pr_detail', 'purchase_receipt',
dn_item_map, pd_item_map, parent_child_map, warehouse_map)
if list(so_item_map.values()):
pd_item_map, parent_child_map, warehouse_map = \
get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
update_pi_items(doc, 'po_detail', 'purchase_order',
so_item_map, pd_item_map, parent_child_map, warehouse_map)
def update_pi_items(doc, detail_field, parent_field, sales_item_map,
purchase_item_map, parent_child_map, warehouse_map):
for item in doc.get('items'):
item.set(detail_field, purchase_item_map.get(sales_item_map.get(item.sales_invoice_item)))
item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item)))
if doc.update_stock:
item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item))
def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map):
for item in doc.get('items'):
item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item))
item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item))
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
def get_delivery_note_details(internal_reference):
so_item_map = {}
si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
filters={'parent': internal_reference})
for d in si_item_details:
so_item_map.setdefault(d.name, d.so_detail)
return so_item_map
def get_sales_invoice_details(internal_reference):
dn_item_map = {}
so_item_map = {}
si_item_details = frappe.get_all('Sales Invoice Item', fields=['name', 'so_detail',
'dn_detail'], filters={'parent': internal_reference})
for d in si_item_details:
if d.dn_detail:
dn_item_map.setdefault(d.name, d.dn_detail)
if d.so_detail:
so_item_map.setdefault(d.name, d.so_detail)
return dn_item_map, so_item_map
def get_pd_details(doctype, sd_detail_map, sd_detail_field):
pd_item_map = {}
accepted_warehouse_map = {}
parent_child_map = {}
pd_item_details = frappe.get_all(doctype,
fields=[sd_detail_field, 'name', 'warehouse', 'parent'], filters={sd_detail_field: ('in', list(sd_detail_map.values()))})
for d in pd_item_details:
pd_item_map.setdefault(d.get(sd_detail_field), d.name)
parent_child_map.setdefault(d.get(sd_detail_field), d.parent)
accepted_warehouse_map.setdefault(d.get(sd_detail_field), d.warehouse)
return pd_item_map, parent_child_map, accepted_warehouse_map
def update_taxes(doc, party=None, party_type=None, company=None, doctype=None, party_address=None,
company_address=None, shipping_address_name=None, master_doctype=None):
# Update Party Details
party_details = get_party_details(party=party, party_type=party_type, company=company,
doctype=doctype, party_address=party_address, company_address=company_address,
shipping_address=shipping_address_name)
# Update taxes and charges if any
doc.taxes_and_charges = party_details.get('taxes_and_charges')
doc.set('taxes', party_details.get('taxes'))
def update_address(doc, address_field, address_display_field, address_name):
doc.set(address_field, address_name)
fetch_values = get_fetch_values(doc.doctype, address_field, address_name)
for key, value in fetch_values.items():
doc.set(key, value)
doc.set(address_display_field, get_address_display(doc.get(address_field)))
@frappe.whitelist()
def get_loyalty_programs(customer):
''' sets applicable loyalty program to the customer or returns a list of applicable programs '''
@ -1694,6 +1854,7 @@ def get_mode_of_payment_info(mode_of_payment, company):
where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
(company, mode_of_payment), as_dict=1)
@frappe.whitelist()
def create_dunning(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount

View File

@ -10,8 +10,8 @@ frappe.listview_settings['Sales Invoice'] = {
"Draft": "grey",
"Unpaid": "orange",
"Paid": "green",
"Return": "darkgrey",
"Credit Note Issued": "darkgrey",
"Return": "gray",
"Credit Note Issued": "gray",
"Unpaid and Discounted": "orange",
"Overdue and Discounted": "red",
"Overdue": "red",

View File

@ -22,6 +22,7 @@ from erpnext.regional.india.utils import get_ewb_data
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
from erpnext.stock.utils import get_incoming_rate
class TestSalesInvoice(unittest.TestCase):
def make(self):
@ -688,7 +689,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertTrue(gle)
def test_pos_gl_entry_with_perpetual_inventory(self):
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
@ -745,7 +746,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(pos_return.get('payments')[0].amount, -1000)
def test_pos_change_amount(self):
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
@ -1573,17 +1574,17 @@ class TestSalesInvoice(unittest.TestCase):
})
sales_invoice = create_sales_invoice(do_not_save=1)
sales_invoice.items[0].project = item_project.project_name
sales_invoice.project = project.project_name
sales_invoice.items[0].project = item_project.name
sales_invoice.project = project.name
sales_invoice.submit()
expected_values = {
"Debtors - _TC": {
"project": project.project_name
"project": project.name
},
"Sales - _TC": {
"project": item_project.project_name
"project": item_project.name
}
}
@ -1770,59 +1771,82 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
# def test_internal_transfer_gl_entry(self):
# ## Create internal transfer account
# account = create_account(account_name="Unrealized Profit",
# parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
def test_internal_transfer_gl_entry(self):
## Create internal transfer account
account = create_account(account_name="Unrealized Profit",
parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
# frappe.db.set_value('Company', '_Test Company with perpetual inventory',
# 'unrealized_profit_loss_account', account)
frappe.db.set_value('Company', '_Test Company with perpetual inventory',
'unrealized_profit_loss_account', account)
# customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
# "_Test Company with perpetual inventory")
customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
"_Test Company with perpetual inventory")
# create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
# "_Test Company with perpetual inventory")
create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
"_Test Company with perpetual inventory")
# si = create_sales_invoice(
# company = "_Test Company with perpetual inventory",
# customer = customer,
# debit_to = "Debtors - TCP1",
# warehouse = "Stores - TCP1",
# income_account = "Sales - TCP1",
# expense_account = "Cost of Goods Sold - TCP1",
# cost_center = "Main - TCP1",
# currency = "INR",
# do_not_save = 1
# )
si = create_sales_invoice(
company = "_Test Company with perpetual inventory",
customer = customer,
debit_to = "Debtors - TCP1",
warehouse = "Stores - TCP1",
income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1",
cost_center = "Main - TCP1",
currency = "INR",
do_not_save = 1
)
# si.selling_price_list = "_Test Price List Rest of the World"
# si.update_stock = 1
# si.items[0].target_warehouse = 'Work In Progress - TCP1'
# add_taxes(si)
# si.save()
# si.submit()
si.selling_price_list = "_Test Price List Rest of the World"
si.update_stock = 1
si.items[0].target_warehouse = 'Work In Progress - TCP1'
add_taxes(si)
si.save()
# target_doc = make_inter_company_transaction("Sales Invoice", si.name)
# target_doc.company = '_Test Company with perpetual inventory'
# target_doc.items[0].warehouse = 'Finished Goods - TCP1'
# add_taxes(target_doc)
# target_doc.save()
# target_doc.submit()
rate = 0.0
for d in si.get('items'):
rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": si.posting_date,
"posting_time": si.posting_time,
"qty": -1 * flt(d.get('stock_qty')),
"serial_no": d.serial_no,
"company": si.company,
"voucher_type": 'Sales Invoice',
"voucher_no": si.name,
"allow_zero_valuation": d.get("allow_zero_valuation")
}, raise_error_if_no_rate=False)
# si_gl_entries = [
# ["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
# ["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
# ]
rate = flt(rate, 2)
# check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
si.submit()
# pi_gl_entries = [
# ["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
# ["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
# ]
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
target_doc.company = '_Test Company with perpetual inventory'
target_doc.items[0].warehouse = 'Finished Goods - TCP1'
add_taxes(target_doc)
target_doc.save()
target_doc.submit()
# check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
tax_amount = flt(rate * (12/100), 2)
si_gl_entries = [
["_Test Account Excise Duty - TCP1", 0.0, tax_amount, nowdate()],
["Unrealized Profit - TCP1", tax_amount, 0.0, nowdate()]
]
check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
pi_gl_entries = [
["_Test Account Excise Duty - TCP1", tax_amount , 0.0, nowdate()],
["Unrealized Profit - TCP1", 0.0, tax_amount, nowdate()]
]
# Sale and Purchase both should be at valuation rate
self.assertEqual(si.items[0].rate, rate)
self.assertEqual(target_doc.items[0].rate, rate)
check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
def test_eway_bill_json(self):
si = make_sales_invoice_for_ewaybill()
@ -1841,7 +1865,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
def test_einvoice_submission_without_irn(self):
# init
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
@ -1857,27 +1881,10 @@ class TestSalesInvoice(unittest.TestCase):
# reset
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
frappe.flags.country = country
def test_einvoice_json(self):
from erpnext.regional.india.e_invoice.utils import make_einvoice
customer_gstin = '27AACCM7806M1Z3'
customer_gstin_dtls = {
'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
}
company_gstin = '27AAECE4835E1ZR'
company_gstin_dtls = {
'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
}
# set cache gstin details to avoid fetching details which will require connection to GSP servers
frappe.local.gstin_cache = {}
frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'
si.items = []
@ -1930,12 +1937,12 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
self.assertEqual(
value_details['TotInvVal'],
value_details['AssVal'] + value_details['CgstVal']
+ value_details['SgstVal'] + value_details['IgstVal']
calculated_invoice_value = \
value_details['AssVal'] + value_details['CgstVal'] \
+ value_details['SgstVal'] + value_details['IgstVal'] \
+ value_details['OthChrg'] - value_details['Discount']
)
self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
self.assertTrue(einvoice['EwbDtls'])

View File

@ -565,11 +565,12 @@
"print_hide": 1
},
{
"depends_on": "eval: parent.is_internal_customer && parent.update_stock",
"fieldname": "target_warehouse",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 1,
"label": "Customer Warehouse (Optional)",
"label": "Target Warehouse",
"no_copy": 1,
"options": "Warehouse",
"print_hide": 1
@ -815,7 +816,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-09-23 19:59:04.879322",
"modified": "2020-12-26 17:25:04.090630",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@ -5,8 +5,6 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from erpnext.controllers.print_settings import print_settings_for_item_table
class SalesInvoiceItem(Document):
def __setup__(self):
print_settings_for_item_table(self)
pass

View File

@ -5,3 +5,25 @@ cur_frm.cscript.tax_table = "Sales Taxes and Charges";
{% include "erpnext/public/js/controllers/accounts.js" %}
frappe.tour['Sales Taxes and Charges Template'] = [
{
fieldname: "title",
title: __("Title"),
description: __("A name by which you will identify this template. You can change this later."),
},
{
fieldname: "company",
title: __("Company"),
description: __("Company for which this tax template will be applicable"),
},
{
fieldname: "is_default",
title: __("Is this Default?"),
description: __("Set this template as the default for all sales transactions"),
},
{
fieldname: "taxes",
title: __("Taxes Table"),
description: __("You can add a row for a tax rule here. These rules can be applied on the net total, or can be a flat amount."),
}
];

View File

@ -34,6 +34,9 @@ def valdiate_taxes_and_charges_template(doc):
validate_disabled(doc)
# Validate with existing taxes and charges template for unique tax category
validate_for_tax_category(doc)
for tax in doc.get("taxes"):
validate_taxes_and_charges(tax)
validate_inclusive_tax(tax, doc)
@ -41,3 +44,7 @@ def valdiate_taxes_and_charges_template(doc):
def validate_disabled(doc):
if doc.is_default and doc.disabled:
frappe.throw(_("Disabled template must not be default template"))
def validate_for_tax_category(doc):
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}):
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))

View File

@ -1,16 +1,18 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.ui.form.on('Shipping Rule', {
refresh: function(frm) {
frm.set_query("cost_center", function() {
return {
filters: {
company: frm.doc.company
}
}
})
frappe.provide('erpnext.accounts.dimensions');
frappe.ui.form.on('Shipping Rule', {
onload: function(frm) {
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
},
refresh: function(frm) {
frm.set_query("account", function() {
return {
filters: {

View File

@ -446,7 +446,7 @@ class Subscription(Document):
if not self.generate_invoice_at_period_start:
return False
if self.is_new_subscription():
if self.is_new_subscription() and getdate() >= getdate(self.current_invoice_start):
return True
# Check invoice dates and make sure it doesn't have outstanding invoices

View File

@ -11,7 +11,7 @@ frappe.listview_settings['Subscription'] = {
} else if(doc.status === 'Unpaid') {
return [__("Unpaid"), "red"];
} else if(doc.status === 'Cancelled') {
return [__("Cancelled"), "darkgrey"];
return [__("Cancelled"), "gray"];
}
}
};

View File

@ -13,7 +13,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
"idx": 0,
"is_complete": 0,
"modified": "2020-07-08 14:06:09.033880",
"modified": "2020-10-30 15:41:15.547225",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts",

View File

@ -1,19 +1,24 @@
{
"action": "Go to Page",
"action_label": "View Chart of Accounts",
"callback_message": "You can continue with the onboarding after exploring this page",
"callback_title": "Awesome Work",
"creation": "2020-05-13 19:58:20.928127",
"description": "# Chart Of Accounts\n\nThe Chart of Accounts is the blueprint of the accounts in your organization.\nIt is a tree view of the names of the Accounts (Ledgers and Groups) that a Company requires to manage its books of accounts. ERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to your needs and legal requirements.\n\nFor each company, Chart of Accounts signifies the way to classify the accounting entries, mostly\nbased on statutory (tax, compliance to government regulations) requirements.\n\nThere's a brief video tutorial about chart of accounts in the next step.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"intro_video_url": "https://www.youtube.com/embed/AcfMCT7wLLo",
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 17:40:28.410447",
"modified": "2020-10-30 14:35:59.474920",
"modified_by": "Administrator",
"name": "Chart of Accounts",
"owner": "Administrator",
"path": "Tree/Account",
"reference_document": "Account",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Review Chart of Accounts",
"validate_action": 0

View File

@ -1,18 +1,19 @@
{
"action": "Create Entry",
"action": "Show Form Tour",
"creation": "2020-05-14 17:53:00.876946",
"description": "# Account Settings\n\nThis is a crucial piece of configuration. There are various account settings in ERPNext to restrict and configure actions in the Accounting module.\n\nThe following settings are avaialble for you to configure\n\n1. Account Freezing \n2. Credit and Overbilling\n3. Invoicing and Tax Automations\n4. Balance Sheet configurations\n\nThere's much more, you can check it all out in this step",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 1,
"is_skipped": 0,
"modified": "2020-05-14 18:06:25.212923",
"modified": "2020-10-19 14:40:55.584484",
"modified_by": "Administrator",
"name": "Configure Account Settings",
"owner": "Administrator",
"reference_document": "Accounts Settings",
"show_form_tour": 0,
"show_full_form": 1,
"title": "Configure Account Settings",
"validate_action": 1

View File

@ -1,18 +1,19 @@
{
"action": "Create Entry",
"creation": "2020-05-14 17:46:41.831517",
"description": "## Who is a Customer?\n\nA customer, who is sometimes known as a client, buyer, or purchaser is the one who receives goods, services, products, or ideas, from a seller for a monetary consideration.\n\nEvery customer needs to be assigned a unique id. Customer name itself can be the id or you can set a naming series for ids to be generated in Selling Settings.\n\nJust like the supplier, let's quickly create a customer.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-06-01 13:16:19.731719",
"modified": "2020-10-30 15:28:46.659660",
"modified_by": "Administrator",
"name": "Create a Customer",
"owner": "Administrator",
"reference_document": "Customer",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Create a Customer",
"validate_action": 1

View File

@ -1,19 +1,21 @@
{
"action": "Create Entry",
"creation": "2020-05-12 18:16:06.624554",
"description": "## Products and Services\n\nDepending on the nature of your business, you might be selling products or services to your clients or even both. \nERPNext is optimized for itemized management of your sales and purchase.\n\nThe **Item Master** is where you can add all your sales items. If you are in services, you can create an Item for each service that you offer. If you run a manufacturing business, the same master is used for keeping a record of raw materials, sub-assemblies etc.\n\nCompleting the Item Master is very essential for the successful implementation of ERPNext. We have a brief video introducing the item master for you, you can watch it in the next step.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"intro_video_url": "https://www.youtube.com/watch?v=Sl5UFA5H5EQ",
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-12 18:30:02.489949",
"modified": "2020-10-30 15:20:30.133495",
"modified_by": "Administrator",
"name": "Create a Product",
"owner": "Administrator",
"reference_document": "Item",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Create a Product",
"title": "Create a Sales Item",
"validate_action": 1
}

View File

@ -1,18 +1,19 @@
{
"action": "Create Entry",
"creation": "2020-05-14 22:09:10.043554",
"description": "## Who is a Supplier?\n\nSuppliers are companies or individuals who provide you with products or services. ERPNext has comprehensive features for purchase cycles. \n\nLet's quickly create a supplier with the minimal details required. You need the name of the supplier, assign the supplier to a group, and select the type of the supplier, viz. Company or Individual.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 22:09:10.043554",
"modified": "2020-10-30 15:26:48.315772",
"modified_by": "Administrator",
"name": "Create a Supplier",
"owner": "Administrator",
"reference_document": "Supplier",
"show_form_tour": 0,
"show_full_form": 0,
"title": "Create a Supplier",
"validate_action": 1

View File

@ -1,18 +1,19 @@
{
"action": "Create Entry",
"creation": "2020-05-14 22:10:07.049704",
"description": "# What's a Purchase Invoice?\n\nA Purchase Invoice is a bill you receive from your Suppliers against which you need to make the payment.\nPurchase Invoice is the exact opposite of your Sales Invoice. Here you accrue expenses to your Supplier. \n\nThe following is what a typical purchase cycle looks like, however you can create a purchase invoice directly as well.\n\n![Purchase Flow](https://docs.erpnext.com/docs/assets/img/accounts/pi-flow.png)\n\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 22:10:07.049704",
"modified": "2020-10-30 15:30:26.337773",
"modified_by": "Administrator",
"name": "Create Your First Purchase Invoice",
"owner": "Administrator",
"reference_document": "Purchase Invoice",
"show_form_tour": 0,
"show_full_form": 1,
"title": "Create Your First Purchase Invoice ",
"validate_action": 1

View File

@ -1,18 +1,19 @@
{
"action": "Create Entry",
"creation": "2020-05-14 17:48:21.019019",
"description": "# All about sales invoice\n\nA Sales Invoice is a bill that you send to your Customers against which the Customer makes the payment. Sales Invoice is an accounting transaction. On submission of Sales Invoice, the system updates the receivable and books income against a Customer Account.\n\nHere's the flow of how a sales invoice is generally created\n\n\n![Sales Flow](https://docs.erpnext.com/docs/assets/img/accounts/so-flow.png)",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 17:48:21.019019",
"modified": "2020-10-16 12:59:16.987507",
"modified_by": "Administrator",
"name": "Create Your First Sales Invoice",
"owner": "Administrator",
"reference_document": "Sales Invoice",
"show_form_tour": 0,
"show_full_form": 1,
"title": "Create Your First Sales Invoice ",
"validate_action": 1

View File

@ -1,18 +1,20 @@
{
"action": "Create Entry",
"action_label": "Make a Sales Tax Template",
"creation": "2020-05-13 19:29:43.844463",
"description": "# Setting up Taxes\n\nAny sophisticated accounting system, including ERPNext will have automatic tax calculations for your transactions. These calculations are based on user defined rules in compliance to local rules and regulations.\n\nERPNext allows this via *Tax Templates*. These templates can be used in Sales Orders and Sales Invoices. Other types of charges that may apply to your invoices (like shipping, insurance etc.) can also be configured as taxes.\n\nFor Tax Accounts that you want to use in the tax templates, go to:\n\n`> Accounting > Taxes > Sales Taxes and Charges Template`\n\nYou can read more about these templates in our documentation [here](https://docs.erpnext.com/docs/user/manual/en/selling/sales-taxes-and-charges-template)\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 17:40:16.014413",
"modified": "2020-10-30 14:54:18.087383",
"modified_by": "Administrator",
"name": "Setup Taxes",
"owner": "Administrator",
"reference_document": "Sales Taxes and Charges Template",
"show_form_tour": 1,
"show_full_form": 1,
"title": "Lets create a Tax Template for Sales ",
"validate_action": 0

View File

@ -39,7 +39,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
party = party_details[party_type.lower()]
if not ignore_permissions and not frappe.has_permission(party_type, "read", party):
if not ignore_permissions and not (frappe.has_permission(party_type, "read", party) or frappe.has_permission(party_type, "select", party)):
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
party = frappe.get_doc(party_type, party)

View File

@ -152,7 +152,7 @@
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
</tr>

View File

@ -1,5 +1,5 @@
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value, fieldmeta,
get_width, get_align_class -%}
get_width, get_align_class with context -%}
{%- macro render_currency(df, doc) -%}
<div class="row {% if df.bold %}important{% endif %} data-field">
@ -63,14 +63,19 @@
<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 %}
{% if not print_settings.compact_item_print or tdf.fieldname in doc.flags.compact_item_fields %}
<td class="{{ get_align_class(tdf) }}" {{ fieldmeta(df) }}>
{% if tdf.fieldname == 'qty' %}
<div class="value">{{ (d[tdf.fieldname])|abs }}</div></td>
{% elif tdf.fieldtype == 'Currency' %}
<div class="value">{{ frappe.utils.fmt_money((d[tdf.fieldname])|abs, currency=doc.currency) }}</div></td>
{% else %}
<div class="value">{{ print_value(tdf, d, doc, visible_columns) }}</div></td>
{% if doc.child_print_templates %}
{%- set child_templates = doc.child_print_templates.get(df.fieldname) -%}
<div class="value">{{ print_value(tdf, d, doc, visible_columns, child_templates) }}</div></td>
{% else %}
<div class="value">{{ print_value(tdf, d, doc, visible_columns) }}</div></td>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}

View File

@ -147,7 +147,6 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit
{
"value": net_asset,
"label": "Total Asset",
"indicator": "Green",
"datatype": "Currency",
"currency": currency
},
@ -155,14 +154,12 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit
"value": net_liability,
"label": "Total Liability",
"datatype": "Currency",
"indicator": "Red",
"currency": currency
},
{
"value": net_equity,
"label": "Total Equity",
"datatype": "Currency",
"indicator": "Blue",
"currency": currency
},
{

View File

@ -49,12 +49,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
elif d.po_detail:
purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, []))
expense_account = d.expense_account or aii_account_map.get(d.company)
expense_account = d.unrealized_profit_loss_account or d.expense_account \
or aii_account_map.get(d.company)
row = {
'item_code': d.item_code,
'item_name': item_record.item_name,
'item_group': item_record.item_group,
'item_name': item_record.item_name if item_record else d.item_name,
'item_group': item_record.item_group if item_record else d.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
@ -315,7 +316,9 @@ def get_items(filters, additional_query_columns):
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice`.unrealized_profit_loss_account,
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,

View File

@ -52,8 +52,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = {
'item_code': d.item_code,
'item_name': item_record.item_name,
'item_group': item_record.item_group,
'item_name': item_record.item_name if item_record else d.item_name,
'item_group': item_record.item_group if item_record else d.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
@ -76,7 +76,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
'company': d.company,
'sales_order': d.sales_order,
'delivery_note': d.delivery_note,
'income_account': d.income_account,
'income_account': d.unrealized_profit_loss_account or d.income_account,
'cost_center': d.cost_center,
'stock_qty': d.stock_qty,
'stock_uom': d.stock_uom
@ -379,9 +379,11 @@ def get_items(filters, additional_query_columns):
select
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.unrealized_profit_loss_account,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,

View File

@ -59,23 +59,111 @@ def validate_filters(filters):
def get_columns(filters):
return [
_("Payment Document") + ":: 100",
_("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":140",
_("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 Posting Date") + ":Date:130",
_("Payment Due Date") + ":Date:130",
_("Debit") + ":Currency:120",
_("Credit") + ":Currency:120",
_("Remarks") + "::150",
_("Age") +":Int:40",
"0-30:Currency:100",
"30-60:Currency:100",
"60-90:Currency:100",
_("90-Above") + ":Currency:100",
_("Delay in payment (Days)") + "::150"
{
"fieldname": "payment_document",
"label": _("Payment Document Type"),
"fieldtype": "Data",
"width": 100
},
{
"fieldname": "payment_entry",
"label": _("Payment Document"),
"fieldtype": "Dynamic Link",
"options": "payment_document",
"width": 160
},
{
"fieldname": "party_type",
"label": _("Party Type"),
"fieldtype": "Data",
"width": 100
},
{
"fieldname": "party",
"label": _("Party"),
"fieldtype": "Dynamic Link",
"options": "party_type",
"width": 160
},
{
"fieldname": "posting_date",
"label": _("Posting Date"),
"fieldtype": "Date",
"width": 100
},
{
"fieldname": "invoice",
"label": _("Invoice"),
"fieldtype": "Link",
"options": "Purchase Invoice" if filters.get("payment_type") == _("Outgoing") else "Sales Invoice",
"width": 160
},
{
"fieldname": "invoice_posting_date",
"label": _("Invoice Posting Date"),
"fieldtype": "Date",
"width": 100
},
{
"fieldname": "due_date",
"label": _("Payment Due Date"),
"fieldtype": "Date",
"width": 100
},
{
"fieldname": "debit",
"label": _("Debit"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "credit",
"label": _("Credit"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "remarks",
"label": _("Remarks"),
"fieldtype": "Data",
"width": 200
},
{
"fieldname": "age",
"label": _("Age"),
"fieldtype": "Int",
"width": 50
},
{
"fieldname": "range1",
"label": "0-30",
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "range2",
"label": "30-60",
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "range3",
"label": "60-90",
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "range4",
"label": _("90 Above"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "delay_in_payment",
"label": _("Delay in payment (Days)"),
"fieldtype": "Int",
"width": 100
}
]
def get_conditions(filters):

View File

@ -59,24 +59,26 @@ def get_report_summary(period_list, periodicity, income, expense, net_profit_los
expense_label = _("Total Expense")
return [
{
"value": net_profit,
"indicator": "Green" if net_profit > 0 else "Red",
"label": profit_label,
"datatype": "Currency",
"currency": currency
},
{
"value": net_income,
"label": income_label,
"datatype": "Currency",
"currency": currency
},
{ "type": "separator", "value": "-"},
{
"value": net_expense,
"label": expense_label,
"datatype": "Currency",
"currency": currency
},
{ "type": "separator", "value": "=", "color": "blue"},
{
"value": net_profit,
"indicator": "Green" if net_profit > 0 else "Red",
"label": profit_label,
"datatype": "Currency",
"currency": currency
}
]

View File

@ -14,13 +14,15 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if not filters: filters = {}
invoice_list = get_invoices(filters, additional_query_columns)
columns, expense_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns)
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts \
= get_columns(invoice_list, additional_table_columns)
if not invoice_list:
msgprint(_("No record found"))
return columns, invoice_list
invoice_expense_map = get_invoice_expense_map(invoice_list)
internal_invoice_map = get_internal_invoice_map(invoice_list)
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
invoice_expense_map, expense_accounts)
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
@ -52,10 +54,17 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
# map expense values
base_net_total = 0
for expense_acc in expense_accounts:
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
if inv.is_internal_supplier and inv.company == inv.represents_company:
expense_amount = 0
else:
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
base_net_total += expense_amount
row.append(expense_amount)
# Add amount in unrealized account
for account in unrealized_profit_loss_accounts:
row.append(flt(internal_invoice_map.get((inv.name, account))))
# net total
row.append(base_net_total or inv.base_net_total)
@ -96,7 +105,8 @@ def get_columns(invoice_list, additional_table_columns):
"width": 80
}
]
expense_accounts = tax_accounts = expense_columns = tax_columns = []
expense_accounts = tax_accounts = expense_columns = tax_columns = unrealized_profit_loss_accounts = \
unrealized_profit_loss_account_columns = []
if invoice_list:
expense_accounts = frappe.db.sql_list("""select distinct expense_account
@ -112,17 +122,25 @@ def get_columns(invoice_list, additional_table_columns):
and parent in (%s) order by account_head""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
and ifnull(unrealized_profit_loss_account, '') != ''
order by unrealized_profit_loss_account""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts]
for account in tax_accounts:
if account not in expense_accounts:
tax_columns.append(account + ":Currency/currency:120")
columns = columns + expense_columns + [_("Net Total") + ":Currency/currency:120"] + tax_columns + \
columns = columns + expense_columns + unrealized_profit_loss_account_columns + \
[_("Net Total") + ":Currency/currency:120"] + tax_columns + \
[_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120",
_("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"]
return columns, expense_accounts, tax_accounts
return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
def get_conditions(filters):
conditions = ""
@ -199,6 +217,19 @@ def get_invoice_expense_map(invoice_list):
return invoice_expense_map
def get_internal_invoice_map(invoice_list):
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
base_net_total as amount from `tabPurchase Invoice` where name in (%s)
and is_internal_supplier = 1 and company = represents_company""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
internal_invoice_map = {}
for d in unrealized_amount_details:
if d.unrealized_profit_loss_account:
internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
return internal_invoice_map
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
tax_details = frappe.db.sql("""
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)

View File

@ -15,13 +15,14 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
if not filters: filters = frappe._dict({})
invoice_list = get_invoices(filters, additional_query_columns)
columns, income_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns)
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(invoice_list, additional_table_columns)
if not invoice_list:
msgprint(_("No record found"))
return columns, invoice_list
invoice_income_map = get_invoice_income_map(invoice_list)
internal_invoice_map = get_internal_invoice_map(invoice_list)
invoice_income_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
invoice_income_map, income_accounts)
#Cost Center & Warehouse Map
@ -70,12 +71,22 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
# map income values
base_net_total = 0
for income_acc in income_accounts:
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
if inv.is_internal_customer and inv.company == inv.represents_company:
income_amount = 0
else:
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
base_net_total += income_amount
row.update({
frappe.scrub(income_acc): income_amount
})
# Add amount in unrealized account
for account in unrealized_profit_loss_accounts:
row.update({
frappe.scrub(account): flt(internal_invoice_map.get((inv.name, account)))
})
# net total
row.update({'net_total': base_net_total or inv.base_net_total})
@ -230,6 +241,8 @@ def get_columns(invoice_list, additional_table_columns):
tax_accounts = []
income_columns = []
tax_columns = []
unrealized_profit_loss_accounts = []
unrealized_profit_loss_account_columns = []
if invoice_list:
income_accounts = frappe.db.sql_list("""select distinct income_account
@ -243,12 +256,18 @@ def get_columns(invoice_list, additional_table_columns):
and parent in (%s) order by account_head""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
from `tabSales Invoice` where docstatus = 1 and name in (%s)
and ifnull(unrealized_profit_loss_account, '') != ''
order by unrealized_profit_loss_account""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
for account in income_accounts:
income_columns.append({
"label": account,
"fieldname": frappe.scrub(account),
"fieldtype": "Currency",
"options": 'currency',
"options": "currency",
"width": 120
})
@ -258,15 +277,24 @@ def get_columns(invoice_list, additional_table_columns):
"label": account,
"fieldname": frappe.scrub(account),
"fieldtype": "Currency",
"options": 'currency',
"options": "currency",
"width": 120
})
for account in unrealized_profit_loss_accounts:
unrealized_profit_loss_account_columns.append({
"label": account,
"fieldname": frappe.scrub(account),
"fieldtype": "Currency",
"options": "currency",
"width": 120
})
net_total_column = [{
"label": _("Net Total"),
"fieldname": "net_total",
"fieldtype": "Currency",
"options": 'currency',
"options": "currency",
"width": 120
}]
@ -301,9 +329,10 @@ def get_columns(invoice_list, additional_table_columns):
}
]
columns = columns + income_columns + net_total_column + tax_columns + total_columns
columns = columns + income_columns + unrealized_profit_loss_account_columns + \
net_total_column + tax_columns + total_columns
return columns, income_accounts, tax_accounts
return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
def get_conditions(filters):
conditions = ""
@ -368,7 +397,8 @@ def get_invoices(filters, additional_query_columns):
return frappe.db.sql("""
select name, posting_date, debit_to, project, customer,
customer_name, owner, remarks, territory, tax_id, customer_group,
base_net_total, base_grand_total, base_rounded_total, outstanding_amount {0}
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
is_internal_customer, represents_company, company {0}
from `tabSales Invoice`
where docstatus = 1 %s order by posting_date desc, name desc""".format(additional_query_columns or '') %
conditions, filters, as_dict=1)
@ -385,6 +415,19 @@ def get_invoice_income_map(invoice_list):
return invoice_income_map
def get_internal_invoice_map(invoice_list):
unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
base_net_total as amount from `tabSales Invoice` where name in (%s)
and is_internal_customer = 1 and company = represents_company""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
internal_invoice_map = {}
for d in unrealized_amount_details:
if d.unrealized_profit_loss_account:
internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
return internal_invoice_map
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
tax_details = frappe.db.sql("""select parent, account_head,
sum(base_tax_amount_after_discount_amount) as tax_amount

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +0,0 @@
{
"cards": [
{
"hidden": 0,
"label": "Crops & Lands",
"links": "[\n {\n \"label\": \"Crop\",\n \"name\": \"Crop\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Crop Cycle\",\n \"name\": \"Crop Cycle\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Location\",\n \"name\": \"Location\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Analytics",
"links": "[\n {\n \"label\": \"Plant Analysis\",\n \"name\": \"Plant Analysis\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Soil Analysis\",\n \"name\": \"Soil Analysis\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Water Analysis\",\n \"name\": \"Water Analysis\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Soil Texture\",\n \"name\": \"Soil Texture\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Weather\",\n \"name\": \"Weather\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Agriculture Analysis Criteria\",\n \"name\": \"Agriculture Analysis Criteria\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Diseases & Fertilizers",
"links": "[\n {\n \"label\": \"Disease\",\n \"name\": \"Disease\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Fertilizer\",\n \"name\": \"Fertilizer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Domains",
"charts": [],
"creation": "2020-03-02 17:23:34.339274",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"idx": 0,
"is_standard": 1,
"label": "Agriculture",
"modified": "2020-04-01 11:28:51.032822",
"modified_by": "Administrator",
"module": "Agriculture",
"name": "Agriculture",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"restrict_to_domain": "Agriculture",
"shortcuts": []
}

View File

@ -48,7 +48,7 @@ class CropCycle(Document):
def import_disease_tasks(self, disease, start_date):
disease_doc = frappe.get_doc('Disease', disease)
self.create_task(disease_doc.treatment_task, self.name, start_date)
self.create_task(disease_doc.treatment_task, self.project, start_date)
def create_project(self, period, crop_tasks):
project = frappe.get_doc({

View File

@ -71,4 +71,4 @@ def check_task_creation():
def check_project_creation():
return True if frappe.db.exists('Project', 'Basil from seed 2017') else False
return True if frappe.db.exists('Project', {'project_name': 'Basil from seed 2017'}) else False

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