diff --git a/.travis.yml b/.travis.yml
index 40afeee8d46..365eb67f3dc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -63,6 +63,7 @@ install:
- tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
- sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
- sudo chmod o+x /usr/local/bin/wkhtmltopdf
+ - sudo apt-get install libcups2-dev
- cd ~/frappe-bench
diff --git a/README.md b/README.md
index 64f8d67d8e8..ed57a17279c 100644
--- a/README.md
+++ b/README.md
@@ -13,9 +13,26 @@
-Includes: Accounting, Inventory, Manufacturing, CRM, Sales, Purchase, Project Management, HRMS. Requires MariaDB.
+ERPNext as a monolith includes the following areas for managing businesses:
-ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & JavaScript.
+1. [Accounting](https://erpnext.com/docs/user/manual/en/accounts)
+1. [Inventory](https://erpnext.com/docs/user/manual/en/stock)
+1. [CRM](https://erpnext.com/docs/user/manual/en/CRM)
+1. [Sales](https://erpnext.com/docs/user/manual/en/selling)
+1. [Purchase](https://erpnext.com/docs/user/manual/en/buying)
+1. [HRMS](https://erpnext.com/docs/user/manual/en/human-resources)
+1. [Project Management](https://erpnext.com/docs/user/manual/en/projects)
+1. [Support](https://erpnext.com/docs/user/manual/en/support)
+1. [Asset Management](https://erpnext.com/docs/user/manual/en/asset)
+1. [Quality Management](https://erpnext.com/docs/user/manual/en/quality-management)
+1. [Manufacturing](https://erpnext.com/docs/user/manual/en/manufacturing)
+1. [Website Management](https://erpnext.com/docs/user/manual/en/website)
+1. [Customize ERPNext](https://erpnext.com/docs/user/manual/en/customize-erpnext)
+1. [And More](https://erpnext.com/docs/user/manual/en/)
+
+ERPNext requires MariaDB.
+
+ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a full-stack web app framework built with Python & JavaScript.
- [User Guide](https://erpnext.com/docs/user)
- [Discussion Forum](https://discuss.erpnext.com/)
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index d031bc5bb17..f40b9575632 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '12.1.8'
+__version__ = '12.2.0'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
index d098d8421c7..43acded3a98 100644
--- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
+++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
@@ -19,6 +19,11 @@ def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_d
else:
chart = frappe._dict(frappe.parse_json(chart))
timespan = chart.timespan
+
+ if chart.timespan == 'Select Date Range':
+ from_date = chart.from_date
+ to_date = chart.to_date
+
timegrain = chart.time_interval
filters = frappe.parse_json(chart.filters_json)
@@ -88,7 +93,8 @@ def get_gl_entries(account, to_date):
fields = ['posting_date', 'debit', 'credit'],
filters = [
dict(posting_date = ('<', to_date)),
- dict(account = ('in', child_accounts))
+ dict(account = ('in', child_accounts)),
+ dict(voucher_type = ('!=', 'Period Closing Voucher'))
],
order_by = 'posting_date asc')
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index 32485a34695..62a8f05c659 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -174,6 +174,8 @@ def make_gl_entries(doc, credit_account, debit_account, against,
# GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries
+ if amount == 0: return
+
gl_entries = []
gl_entries.append(
doc.get_gl_dict({
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index 7cca8d2003b..cf1748f6a7f 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -109,15 +109,16 @@ class Account(NestedSet):
if not descendants: return
parent_acc_name_map = {}
- parent_acc_name = frappe.db.get_value('Account', self.parent_account, "account_name")
+ parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
+ ["account_name", "account_number"])
for d in frappe.db.get_values('Account',
- {"company": ["in", descendants], "account_name": parent_acc_name},
+ { "company": ["in", descendants], "account_name": parent_acc_name,
+ "account_number": parent_acc_number },
["company", "name"], as_dict=True):
parent_acc_name_map[d["company"]] = d["name"]
-
if not parent_acc_name_map: return
- self.create_account_for_child_company(parent_acc_name_map, descendants)
+ self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
def validate_group_or_ledger(self):
if self.get("__islocal"):
@@ -159,7 +160,7 @@ class Account(NestedSet):
if frappe.db.get_value("GL Entry", {"account": self.name}):
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
- def create_account_for_child_company(self, parent_acc_name_map, descendants):
+ def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
for company in descendants:
if not parent_acc_name_map.get(company):
frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json
index 8856c8cc90f..a8afb55df6f 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json
@@ -1,465 +1,466 @@
{
- "country_code": "ae",
- "name": "U.A.E - Chart of Accounts",
+ "country_code": "ae",
+ "name": "U.A.E - Chart of Accounts",
"tree": {
"Assets": {
"Current Assets": {
"Accounts Receivable": {
"Corporate Credit Cards": {
"account_type": "Receivable"
- },
+ },
"Other Receivable": {
"Accrued Rebates Due from Suppliers": {
"account_type": "Receivable"
- },
+ },
"Accrued Income from Suppliers": {
"account_type": "Receivable"
- },
+ },
"Other Debtors": {
"account_type": "Receivable"
- },
+ },
"account_type": "Receivable"
- },
+ },
"Post Dated Cheques Received": {
"account_type": "Receivable"
- },
+ },
"Staff Receivable": {
"account_type": "Receivable"
- },
+ },
"Trade Receivable": {
"account_type": "Receivable"
- },
+ },
"Trade in Opening Fees": {
"account_type": "Receivable"
- },
+ },
"account_type": "Receivable"
- },
+ },
"Cash in Hand & Banks": {
"Banks": {
- "Bank Margin On LC & LG": {},
- "Banks Blocked Deposits": {},
- "Banks Call Deposit Accounts": {},
+ "Bank Margin On LC & LG": {},
+ "Banks Blocked Deposits": {},
+ "Banks Call Deposit Accounts": {},
"Banks Current Accounts": {
"account_type": "Bank"
- },
+ },
"account_type": "Bank"
- },
+ },
"Cash in Hand": {
"Cash in Safe": {
"Main Safe": {
"account_type": "Cash"
- },
+ },
"Main Safe - Foreign Currency": {
"account_type": "Cash"
}
- },
+ },
"Petty Cash": {
"Petty Cash - Administration": {
"account_type": "Cash"
- },
+ },
"Petty Cash - Others": {
"account_type": "Cash"
}
- },
+ },
"account_type": "Cash"
- },
+ },
"Cash in Transit": {
"Credit Cards": {
"Gateway Credit Cards": {
"account_type": "Bank"
- },
+ },
"Manual Visa & Master Cards": {
"account_type": "Bank"
- },
+ },
"PayPal Account": {
"account_type": "Bank"
- },
+ },
"Visa & Master Credit Cards": {
"account_type": "Bank"
}
}
}
- },
+ },
"Inventory": {
"Consigned Stock": {
- "Handling Difference in Inventory": {
- "account_type": "Stock Adjustment"
- },
+ "Handling Difference in Inventory": {},
"Items Delivered to Customs on temporary Base": {}
- },
+ },
"Stock in Hand": {
"account_type": "Stock"
}
- },
+ },
"Preliminary and Preoperating Expenses": {
"Preoperating Expenses": {}
- },
+ },
"Prepayments & Deposits": {
"Deposits": {
- "Deposit - Office Rent": {},
- "Deposit Others": {},
- "Deposit to Immigration (Visa)": {},
+ "Deposit - Office Rent": {},
+ "Deposit Others": {},
+ "Deposit to Immigration (Visa)": {},
"Deposits - Customs": {}
- },
+ },
"Prepaid Taxes": {
- "Sales Taxes Receivables": {},
+ "Sales Taxes Receivables": {},
"Withholding Tax Receivables": {}
- },
+ },
"Prepayments": {
- "Other Prepayments": {},
- "PrePaid Advertisement Expenses": {},
- "Prepaid Bank Guarantee": {},
- "Prepaid Consultancy Fees": {},
- "Prepaid Employees Housing": {},
- "Prepaid Finance charge for Loans": {},
- "Prepaid Legal Fees": {},
- "Prepaid License Fees": {},
- "Prepaid Life Insurance": {},
- "Prepaid Maintenance": {},
- "Prepaid Medical Insurance": {},
- "Prepaid Office Rent": {},
- "Prepaid Other Insurance": {},
- "Prepaid Schooling Fees": {},
- "Prepaid Site Hosting Fees": {},
+ "Other Prepayments": {},
+ "PrePaid Advertisement Expenses": {},
+ "Prepaid Bank Guarantee": {},
+ "Prepaid Consultancy Fees": {},
+ "Prepaid Employees Housing": {},
+ "Prepaid Finance charge for Loans": {},
+ "Prepaid Legal Fees": {},
+ "Prepaid License Fees": {},
+ "Prepaid Life Insurance": {},
+ "Prepaid Maintenance": {},
+ "Prepaid Medical Insurance": {},
+ "Prepaid Office Rent": {},
+ "Prepaid Other Insurance": {},
+ "Prepaid Schooling Fees": {},
+ "Prepaid Site Hosting Fees": {},
"Prepaid Sponsorship Fees": {}
}
}
- },
+ },
"Long Term Assets": {
"Fixed Assets": {
"Accumulated Depreciation": {
"Acc. Depreciation of Motor Vehicles": {
"account_type": "Accumulated Depreciation"
- },
+ },
"Acc. Deprn.Computer Hardware & Software": {
"account_type": "Accumulated Depreciation"
- },
+ },
"Acc.Deprn.of Furniture & Office Equipment": {
"account_type": "Accumulated Depreciation"
- },
+ },
"Amortisation on Leasehold Improvement": {
"account_type": "Accumulated Depreciation"
- },
+ },
"account_type": "Accumulated Depreciation"
- },
+ },
"Fixed Assets (Cost Price)": {
"Computer Hardware & Software": {
"account_type": "Fixed Asset"
- },
+ },
"Furniture and Equipment": {
"account_type": "Fixed Asset"
- },
- "Leasehold Improvement": {},
+ },
+ "Leasehold Improvement": {},
"Motor Vehicles": {
"account_type": "Fixed Asset"
- },
- "Work In Progress": {},
+ },
+ "Work In Progress": {},
"account_type": "Fixed Asset"
}
- },
+ },
"Intangible Assets": {
- "Computer Card Renewal": {},
- "Disposal of Outlets": {},
+ "Computer Card Renewal": {},
+ "Disposal of Outlets": {},
"Registration of Trademarks": {}
- },
- "Intercompany Accounts": {},
+ },
+ "Intercompany Accounts": {},
"Investments": {
"Investments in Subsidiaries": {}
}
- },
+ },
"root_type": "Asset"
- },
+ },
"Closing And Temporary Accounts": {
"Closing Accounts": {
"Closing Account": {}
- },
+ },
"root_type": "Liability"
- },
+ },
"Expenses": {
"Commercial Expenses": {
- "Consultancy Fees": {},
+ "Consultancy Fees": {},
"Provision for Doubtful Debts": {}
- },
+ },
"Cost of Sale": {
"Cost Of Goods Sold": {
- "Cost Of Goods Sold I/C Sales": {},
+ "Cost Of Goods Sold I/C Sales": {},
"Cost of Goods Sold in Trading": {
"account_type": "Cost of Goods Sold"
- },
+ },
"account_type": "Cost of Goods Sold"
- },
+ },
"Expenses Included In Valuation": {
"account_type": "Expenses Included In Valuation"
+ },
+ "Stock Adjustment": {
+ "account_type": "Stock Adjustment"
}
- },
+ },
"Depreciation": {
"Depreciation & Amortization": {
- "Amortization on Leasehold Improvement": {},
+ "Amortization on Leasehold Improvement": {},
"Depreciation Of Computer Hard & Soft": {
"account_type": "Depreciation"
- },
+ },
"Depreciation Of Furniture & Office Equipment\n\t\t\t": {
"account_type": "Depreciation"
- },
+ },
"Depreciation Of Motor Vehicles": {
"account_type": "Depreciation"
}
}
- },
+ },
"Direct Expenses": {
"Financial Charges": {
- "Air Miles Card Charges": {},
- "Amex Credit Cards Charges": {},
- "Bank Finance & Loan Charges": {},
- "Credit Card Charges": {},
- "Credit Card Swipe Charges": {},
+ "Air Miles Card Charges": {},
+ "Amex Credit Cards Charges": {},
+ "Bank Finance & Loan Charges": {},
+ "Credit Card Charges": {},
+ "Credit Card Swipe Charges": {},
"PayPal Charges": {}
}
- },
+ },
"MISC Charges": {
"Other Charges": {
"Capital Loss": {
- "Disposal of Business Branch": {},
- "Loss On Fixed Assets Disposal": {},
+ "Disposal of Business Branch": {},
+ "Loss On Fixed Assets Disposal": {},
"Loss on Difference on Exchange": {}
- },
+ },
"Other Non Operating Exp": {
"Other Non Operating Expenses": {}
- },
+ },
"Previous Year Adjustments": {
"Previous Year Adjustments Account": {}
- },
+ },
"Royalty Fees": {
"Royalty to Parent Co.": {}
- },
+ },
"Tax / Zakat Expenses": {
"Income Tax": {
"account_type": "Tax"
- },
- "Zakat": {},
+ },
+ "Zakat": {},
"account_type": "Tax"
}
}
- },
+ },
"Share Resources": {
"Share Resource Expenses Account": {}
- },
+ },
"Store Operating Expenses": {
"Selling, General & Admin Expenses": {
"Advertising Expenses": {
"Other - Advertising Expenses": {}
- },
+ },
"Bank & Finance Charges": {
"Other Bank Charges": {}
- },
+ },
"Communications": {
- "Courier": {},
- "Others - Communication": {},
- "Telephone": {},
+ "Courier": {},
+ "Others - Communication": {},
+ "Telephone": {},
"Web Site Hosting Fees": {}
- },
+ },
"Office & Various Expenses": {
- "Cleaning": {},
- "Conveyance Expenses": {},
- "Gifts & Donations": {},
- "Insurance": {},
- "Kitchen and Buffet Expenses": {},
- "Maintenance": {},
- "Others - Office Various Expenses": {},
- "Security & Guard": {},
- "Stationary From Suppliers": {},
- "Stationary Out Of Stock": {},
- "Subscriptions": {},
- "Training": {},
+ "Cleaning": {},
+ "Conveyance Expenses": {},
+ "Gifts & Donations": {},
+ "Insurance": {},
+ "Kitchen and Buffet Expenses": {},
+ "Maintenance": {},
+ "Others - Office Various Expenses": {},
+ "Security & Guard": {},
+ "Stationary From Suppliers": {},
+ "Stationary Out Of Stock": {},
+ "Subscriptions": {},
+ "Training": {},
"Vehicle Expenses": {}
- },
+ },
"Personnel Cost": {
- "Basic Salary": {},
- "End Of Service Indemnity": {},
- "Housing Allowance": {},
- "Leave Salary": {},
- "Leave Ticket": {},
- "Life Insurance": {},
- "Medical Insurance": {},
- "Personnel Cost Others": {},
- "Sales Commission": {},
- "Staff School Allowances": {},
- "Transportation Allowance": {},
- "Uniform": {},
+ "Basic Salary": {},
+ "End Of Service Indemnity": {},
+ "Housing Allowance": {},
+ "Leave Salary": {},
+ "Leave Ticket": {},
+ "Life Insurance": {},
+ "Medical Insurance": {},
+ "Personnel Cost Others": {},
+ "Sales Commission": {},
+ "Staff School Allowances": {},
+ "Transportation Allowance": {},
+ "Uniform": {},
"Visa Expenses": {}
- },
+ },
"Professional & Legal Fees": {
- "Audit Fees": {},
- "Legal fees": {},
- "Others - Professional Fees": {},
- "Sponsorship Fees": {},
+ "Audit Fees": {},
+ "Legal fees": {},
+ "Others - Professional Fees": {},
+ "Sponsorship Fees": {},
"Trade License Fees": {}
- },
+ },
"Provision & Write Off": {
- "Amortisation of Preoperating Expenses": {},
- "Cash Shortage": {},
- "Others - Provision & Write off": {},
- "Write Off Inventory": {},
+ "Amortisation of Preoperating Expenses": {},
+ "Cash Shortage": {},
+ "Others - Provision & Write off": {},
+ "Write Off Inventory": {},
"Write Off Receivables & Payables": {}
- },
+ },
"Rent Expenses": {
- "Office Rent": {},
+ "Office Rent": {},
"Warehouse Rent": {}
- },
+ },
"Travel Expenses": {
- "Air tickets": {},
- "Hotel": {},
- "Meals": {},
- "Others": {},
+ "Air tickets": {},
+ "Hotel": {},
+ "Meals": {},
+ "Others": {},
"Per Diem": {}
- },
+ },
"Utilities": {
- "Other Utility Cahrges": {},
+ "Other Utility Cahrges": {},
"Water & Electricity": {}
}
}
- },
+ },
"root_type": "Expense"
- },
+ },
"Liabilities": {
"Current Liabilities": {
"Accounts Payable": {
"Payables": {
"Advance Payable to Suppliers": {
"account_type": "Payable"
- },
+ },
"Consigned Payable": {
"account_type": "Payable"
- },
+ },
"Other Payable": {
"account_type": "Payable"
- },
+ },
"Post Dated Cheques Paid": {
"account_type": "Payable"
- },
- "Staff Payable": {},
+ },
+ "Staff Payable": {},
"Suppliers Price Protection": {
"account_type": "Payable"
- },
+ },
"Trade Payable": {
"account_type": "Payable"
- },
+ },
"account_type": "Payable"
}
- },
+ },
"Accruals & Provisions": {
"Accruals": {
"Accrued Personnel Cost": {
- "Accrued - Commissions": {},
- "Accrued - Leave Salary": {},
- "Accrued - Leave Tickets": {},
- "Accrued - Salaries": {},
- "Accrued Other Personnel Cost": {},
- "Accrued Salaries Increment": {},
+ "Accrued - Commissions": {},
+ "Accrued - Leave Salary": {},
+ "Accrued - Leave Tickets": {},
+ "Accrued - Salaries": {},
+ "Accrued Other Personnel Cost": {},
+ "Accrued Salaries Increment": {},
"Accrued-Staff Bonus": {}
}
- },
+ },
"Accrued Expenses": {
"Accrued Other Expenses": {
- "Accrued - Audit Fees": {},
- "Accrued - Office Rent": {},
- "Accrued - Sponsorship": {},
- "Accrued - Telephone": {},
- "Accrued - Utilities": {},
+ "Accrued - Audit Fees": {},
+ "Accrued - Office Rent": {},
+ "Accrued - Sponsorship": {},
+ "Accrued - Telephone": {},
+ "Accrued - Utilities": {},
"Accrued Others": {}
}
- },
+ },
"Other Current Liabilities": {
- "Accrued Dubai Customs": {},
- "Deferred income": {},
+ "Accrued Dubai Customs": {},
+ "Deferred income": {},
"Shipping & Handling": {}
- },
+ },
"Provisions": {
"Tax Payables": {
- "Income Tax Payable": {},
- "Sales Tax Payable": {},
+ "Income Tax Payable": {},
+ "Sales Tax Payable": {},
"Withholding Tax Payable": {}
}
- },
+ },
"Short Term Loan": {}
- },
+ },
"Duties and Taxes": {
- "account_type": "Tax",
+ "account_type": "Tax",
"is_group": 1
- },
+ },
"Reservations & Credit Notes": {
"Credit Notes": {
- "Credit Notes to Customers": {},
+ "Credit Notes to Customers": {},
"Reservations": {}
}
- },
+ },
"Stock Liabilities": {
"Stock Received But Not Billed": {
"account_type": "Stock Received But Not Billed"
}
- },
+ },
"Unearned Income": {}
- },
+ },
"Long Term Liabilities": {
"Long Term Loans & Provisions": {}
- },
+ },
"root_type": "Liability"
- },
+ },
"Revenue": {
"Direct Revenue": {
"Other Direct Revenue": {
"Other Revenue - Operating": {
- "Advertising Income": {},
- "Branding Income": {},
- "Early Setmt Margin from Suppliers": {},
- "Marketing Rebate from Suppliers": {},
- "Rebate from Suppliers": {},
- "Service Income": {},
+ "Advertising Income": {},
+ "Branding Income": {},
+ "Early Setmt Margin from Suppliers": {},
+ "Marketing Rebate from Suppliers": {},
+ "Rebate from Suppliers": {},
+ "Service Income": {},
"Space Rental Income": {}
}
}
- },
+ },
"Indirect Revenue": {
"Other Indirect Revenue": {
- "Capital Gain": {},
- "Excess In Till": {},
- "Gain On Difference Of Exchange": {},
- "Management Consultancy Fees": {},
+ "Capital Gain": {},
+ "Excess In Till": {},
+ "Gain On Difference Of Exchange": {},
+ "Management Consultancy Fees": {},
"Other Income": {}
- },
+ },
"Other Revenue - Non Operating": {
- "Interest Revenue": {},
- "Interest from FD": {},
- "Products Listing Fees from Suppliers": {},
+ "Interest Revenue": {},
+ "Interest from FD": {},
+ "Products Listing Fees from Suppliers": {},
"Trade Opening Fees from suppliers": {}
}
- },
+ },
"Sales": {
"Sales from Other Regions": {
"Sales from Other Region": {}
- },
+ },
"Sales of same region": {
- "Management Consultancy Fees 1": {},
- "Sales Account": {},
+ "Management Consultancy Fees 1": {},
+ "Sales Account": {},
"Sales of I/C": {}
}
- },
+ },
"root_type": "Income"
- },
+ },
"Share Holder Equity": {
"Capital": {
- "Contributed Capital": {},
- "Share Capital": {},
- "Shareholders Current A/c": {},
- "Sub Ordinated Loan": {},
+ "Contributed Capital": {},
+ "Share Capital": {},
+ "Shareholders Current A/c": {},
+ "Sub Ordinated Loan": {},
"Treasury Stocks": {}
- },
+ },
"Retained Earnings": {
- "Current Year Results": {},
- "Dividends Paid": {},
+ "Current Year Results": {},
+ "Dividends Paid": {},
"Previous Years Results": {}
- },
- "account_type": "Equity",
+ },
+ "account_type": "Equity",
"root_type": "Equity"
}
}
diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py
index 4ee55736fee..dc23b2b2d05 100644
--- a/erpnext/accounts/doctype/account/test_account.py
+++ b/erpnext/accounts/doctype/account/test_account.py
@@ -160,7 +160,7 @@ def _make_test_records(verbose):
["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"]
]
- for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"]]:
+ for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"], ["_Test Company with perpetual inventory", "TCP1"]]:
test_objects = make_test_objects("Account", [{
"doctype": "Account",
"account_name": account_name,
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index af51fc5d8e5..522ed4ffa46 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -24,6 +24,11 @@ class AccountingDimension(Document):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg)
+ exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
+
+ if exists and self.is_new():
+ frappe.throw("Document Type already used as a dimension")
+
def after_insert(self):
if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
@@ -60,7 +65,8 @@ def make_dimension_in_accounting_doctypes(doc):
"label": doc.label,
"fieldtype": "Link",
"options": doc.document_type,
- "insert_after": insert_after_field
+ "insert_after": insert_after_field,
+ "owner": "Administrator"
}
if doctype == "Budget":
diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py
index 180460c091c..f48d6dfc953 100644
--- a/erpnext/accounts/doctype/accounting_period/accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py
@@ -41,8 +41,8 @@ class AccountingPeriod(Document):
def get_doctypes_for_closing(self):
docs_for_closing = []
- doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", "Bank Reconciliation",
- "Asset", "Purchase Order", "Sales Order", "Leave Application", "Leave Allocation", "Stock Entry"]
+ doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
+ "Bank Reconciliation", "Asset", "Stock Entry"]
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype)
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index 3222aeb0855..2473d715d0d 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -15,8 +15,8 @@ class AccountsSettings(Document):
frappe.clear_cache()
def validate(self):
- for f in ["add_taxes_from_item_tax_template"]:
- frappe.db.set_default(f, self.get(f, ""))
+ frappe.db.set_default("add_taxes_from_item_tax_template",
+ self.get("add_taxes_from_item_tax_template", 0))
self.validate_stale_days()
self.enable_payment_schedule_in_print()
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py
index deedafdfb5d..33ae45439e7 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py
@@ -15,8 +15,8 @@ def upload_bank_statement():
with open(frappe.uploaded_file, "rb") as upfile:
fcontent = upfile.read()
else:
- from frappe.utils.file_manager import get_uploaded_content
- fname, fcontent = get_uploaded_content()
+ fcontent = frappe.local.uploaded_file
+ fname = frappe.local.uploaded_filename
if frappe.safe_encode(fname).lower().endswith("csv".encode('utf-8')):
from frappe.utils.csvutils import read_csv_content
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index 9bf5887b38f..34070b01aea 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -185,7 +185,8 @@ def validate_account_types(accounts):
return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))
account_types_for_group = ["Bank", "Cash", "Stock"]
- account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] not in ('', 1)]
+ # fix logic bug
+ account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1]
missing = list(set(account_types_for_group) - set(account_groups))
if missing:
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py
index 584e11c53f6..0294e78111c 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/cost_center.py
@@ -18,6 +18,7 @@ class CostCenter(NestedSet):
def validate(self):
self.validate_mandatory()
+ self.validate_parent_cost_center()
def validate_mandatory(self):
if self.cost_center_name != self.company and not self.parent_cost_center:
@@ -25,6 +26,12 @@ class CostCenter(NestedSet):
elif self.cost_center_name == self.company and self.parent_cost_center:
frappe.throw(_("Root cannot have a parent cost center"))
+ def validate_parent_cost_center(self):
+ if self.parent_cost_center:
+ if not frappe.db.get_value('Cost Center', self.parent_cost_center, 'is_group'):
+ frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format(
+ frappe.bold(self.parent_cost_center)))
+
def convert_group_to_ledger(self):
if self.check_if_child_exists():
frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py
index c4fad753756..8f23d906760 100644
--- a/erpnext/accounts/doctype/cost_center/test_cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py
@@ -1,12 +1,26 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-
-
+import unittest
import frappe
+
test_records = frappe.get_test_records('Cost Center')
+class TestCostCenter(unittest.TestCase):
+ def test_cost_center_creation_against_child_node(self):
+ if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
+ frappe.get_doc(test_records[1]).insert()
+
+ cost_center = frappe.get_doc({
+ 'doctype': 'Cost Center',
+ 'cost_center_name': '_Test Cost Center 3',
+ 'parent_cost_center': '_Test Cost Center 2 - _TC',
+ 'is_group': 0,
+ 'company': '_Test Company'
+ })
+
+ self.assertRaises(frappe.ValidationError, cost_center.save)
def create_cost_center(**args):
args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.js b/erpnext/accounts/doctype/coupon_code/coupon_code.js
index 0bf097f8d55..da3a9f8132f 100644
--- a/erpnext/accounts/doctype/coupon_code/coupon_code.js
+++ b/erpnext/accounts/doctype/coupon_code/coupon_code.js
@@ -2,6 +2,15 @@
// For license information, please see license.txt
frappe.ui.form.on('Coupon Code', {
+ setup: function(frm) {
+ frm.set_query("pricing_rule", function() {
+ return {
+ filters: [
+ ["Pricing Rule","coupon_code_based", "=", "1"]
+ ]
+ };
+ });
+ },
coupon_name:function(frm){
if (frm.doc.__islocal===1) {
frm.trigger("make_coupon_code");
diff --git a/erpnext/accounts/doctype/coupon_code/coupon_code.json b/erpnext/accounts/doctype/coupon_code/coupon_code.json
index fafc63531f0..7dc5e9dc787 100644
--- a/erpnext/accounts/doctype/coupon_code/coupon_code.json
+++ b/erpnext/accounts/doctype/coupon_code/coupon_code.json
@@ -24,6 +24,7 @@
],
"fields": [
{
+ "description": "e.g. \"Summer Holiday 2019 Offer 20\"",
"fieldname": "coupon_name",
"fieldtype": "Data",
"label": "Coupon Name",
@@ -50,7 +51,7 @@
"fieldtype": "Column Break"
},
{
- "description": "To be used to get discount",
+ "description": "unique e.g. SAVE20 To be used to get discount",
"fieldname": "coupon_code",
"fieldtype": "Data",
"label": "Coupon Code",
@@ -62,12 +63,13 @@
"fieldname": "pricing_rule",
"fieldtype": "Link",
"label": "Pricing Rule",
- "options": "Pricing Rule"
+ "options": "Pricing Rule",
+ "reqd": 1
},
{
"fieldname": "uses",
"fieldtype": "Section Break",
- "label": "Uses"
+ "label": "Validity and Usage"
},
{
"fieldname": "valid_from",
@@ -113,7 +115,7 @@
"read_only": 1
}
],
- "modified": "2019-10-15 14:12:22.686986",
+ "modified": "2019-10-19 14:48:14.602481",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Coupon Code",
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 078e05816db..041e419752b 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -29,7 +29,6 @@ class GLEntry(Document):
self.validate_and_set_fiscal_year()
self.pl_must_have_cost_center()
self.validate_cost_center()
- self.validate_dimensions_for_pl_and_bs()
if not self.flags.from_repost:
self.check_pl_account()
@@ -39,6 +38,7 @@ class GLEntry(Document):
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
if not from_repost:
self.validate_account_details(adv_adj)
+ self.validate_dimensions_for_pl_and_bs()
check_freezing_date(self.posting_date, adv_adj)
validate_frozen_account(self.account, adv_adj)
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 11d847d821e..3604b60b751 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -190,7 +190,6 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
if(jvd.reference_type==="Employee Advance") {
return {
filters: {
- 'status': ['=', 'Unpaid'],
'docstatus': 1
}
};
@@ -398,7 +397,7 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) {
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
args: {
"account_type": (doc.voucher_type=="Bank Entry" ?
- "Bank" : (doc.voucher_type=="Cash" ? "Cash" : null)),
+ "Bank" : (doc.voucher_type=="Cash Entry" ? "Cash" : null)),
"company": doc.company
},
callback: function(r) {
@@ -570,7 +569,7 @@ $.extend(erpnext.journal_entry, {
},
{fieldtype: "Date", fieldname: "posting_date", label: __("Date"), reqd: 1,
default: frm.doc.posting_date},
- {fieldtype: "Small Text", fieldname: "user_remark", label: __("User Remark"), reqd: 1},
+ {fieldtype: "Small Text", fieldname: "user_remark", label: __("User Remark")},
{fieldtype: "Select", fieldname: "naming_series", label: __("Series"), reqd: 1,
options: naming_series_options, default: naming_series_default},
]
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index e25942ca34d..88973373ed7 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -968,7 +968,7 @@ def get_exchange_rate(posting_date, account=None, account_currency=None, company
# The date used to retreive the exchange rate here is the date passed
# in as an argument to this function.
- elif (not exchange_rate or exchange_rate==1) and account_currency and posting_date:
+ elif (not exchange_rate or flt(exchange_rate)==1) and account_currency and posting_date:
exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
else:
exchange_rate = 1
diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
index ab811d81b25..c3f95fa42ec 100644
--- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
+++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
@@ -201,7 +201,7 @@
"fieldname": "reference_type",
"fieldtype": "Select",
"label": "Reference Type",
- "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting"
+ "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees"
},
{
"fieldname": "reference_name",
@@ -281,4 +281,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
index 4a7406e0cb4..341884c1901 100644
--- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
@@ -8,10 +8,12 @@ import unittest
from frappe.utils import today, cint, flt, getdate
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
from erpnext.accounts.party import get_dashboard_info
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
class TestLoyaltyProgram(unittest.TestCase):
@classmethod
def setUpClass(self):
+ set_perpetual_inventory(0)
# create relevant item, customer, loyalty program, etc
create_records()
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index ce8aba75b2c..54464e71c4e 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -32,8 +32,10 @@ class OpeningInvoiceCreationTool(Document):
})
invoices_summary.update({company: _summary})
- paid_amount.append(invoice.paid_amount)
- outstanding_amount.append(invoice.outstanding_amount)
+ if invoice.paid_amount:
+ paid_amount.append(invoice.paid_amount)
+ if invoice.outstanding_amount:
+ outstanding_amount.append(invoice.outstanding_amount)
if paid_amount or outstanding_amount:
max_count.update({
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 1e0b1bcbf16..2192b7bf989 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -554,7 +554,7 @@ frappe.ui.form.on('Payment Entry', {
frappe.flags.allocate_payment_amount = true;
frm.events.validate_filters_data(frm, filters);
frm.events.get_outstanding_documents(frm, filters);
- }, __("Filters"), __("Get Outstanding Invoices"));
+ }, __("Filters"), __("Get Outstanding Documents"));
},
validate_filters_data: function(frm, filters) {
@@ -652,14 +652,16 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
) {
if(total_positive_outstanding > total_negative_outstanding)
- frm.set_value("paid_amount",
- total_positive_outstanding - total_negative_outstanding);
+ if (!frm.doc.paid_amount)
+ frm.set_value("paid_amount",
+ total_positive_outstanding - total_negative_outstanding);
} else if (
total_negative_outstanding &&
total_positive_outstanding < total_negative_outstanding
) {
- frm.set_value("received_amount",
- total_negative_outstanding - total_positive_outstanding);
+ if (!frm.doc.received_amount)
+ frm.set_value("received_amount",
+ total_negative_outstanding - total_positive_outstanding);
}
}
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index a85eccd30af..997937738b6 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -62,6 +62,7 @@
"dimension_col_break",
"cost_center",
"section_break_12",
+ "status",
"remarks",
"column_break_16",
"letter_head",
@@ -331,6 +332,7 @@
"label": "Reference"
},
{
+ "depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_invoice",
"fieldtype": "Button",
"label": "Get Outstanding Invoice"
@@ -563,10 +565,18 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
+ },
+ {
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "\nDraft\nSubmitted\nCancelled",
+ "read_only": 1
}
],
"is_submittable": 1,
- "modified": "2019-05-27 15:53:21.108857",
+ "modified": "2019-12-08 13:02:30.016610",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 89aaffbc2d1..214d6088667 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -61,6 +61,7 @@ class PaymentEntry(AccountsController):
self.validate_duplicate_entry()
self.validate_allocated_amount()
self.ensure_supplier_is_not_blocked()
+ self.set_status()
def on_submit(self):
self.setup_party_account_field()
@@ -70,6 +71,7 @@ class PaymentEntry(AccountsController):
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_expense_claim()
+ self.set_status()
def on_cancel(self):
@@ -79,6 +81,7 @@ class PaymentEntry(AccountsController):
self.update_advance_paid()
self.update_expense_claim()
self.delink_advance_entry_references()
+ self.set_status()
def update_outstanding_amounts(self):
self.set_missing_ref_details(force=True)
@@ -275,6 +278,14 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
.format(d.reference_name, dr_or_cr))
+ def set_status(self):
+ if self.docstatus == 2:
+ self.status = 'Cancelled'
+ elif self.docstatus == 1:
+ self.status = 'Submitted'
+ else:
+ self.status = 'Draft'
+
def set_amounts(self):
self.set_amounts_in_company_currency()
self.set_total_allocated_amount()
@@ -900,7 +911,10 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
else:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
- party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
+ if dt not in ("Sales Invoice", "Purchase Invoice"):
+ party_account_currency = get_account_currency(party_account)
+ else:
+ party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
# payment type
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees") and doc.outstanding_amount > 0)) \
@@ -920,9 +934,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt in ("Expense Claim"):
- grand_total = doc.total_sanctioned_amount
- outstanding_amount = doc.total_sanctioned_amount \
- - doc.total_amount_reimbursed - flt(doc.total_advance_amount)
+ grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
+ outstanding_amount = doc.grand_total \
+ - doc.total_amount_reimbursed
elif dt == "Employee Advance":
grand_total = doc.advance_amount
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 4665d755100..2c04a27b0cd 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -23,6 +23,8 @@ class PaymentReconciliation(Document):
if self.party_type in ["Customer", "Supplier"]:
dr_or_cr_notes = self.get_dr_or_cr_notes()
+ else:
+ dr_or_cr_notes = []
self.add_payment_entries(payment_entries + journal_entries + dr_or_cr_notes)
@@ -90,7 +92,8 @@ class PaymentReconciliation(Document):
FROM `tab{doc}`, `tabGL Entry`
WHERE
(`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no)
- and `tab{doc}`.is_return = 1 and `tabGL Entry`.against_voucher_type = %(voucher_type)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
GROUP BY `tab{doc}`.name
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js
index e2510f675f3..e1e43140c01 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.js
+++ b/erpnext/accounts/doctype/payment_request/payment_request.js
@@ -2,6 +2,16 @@ cur_frm.add_fetch("payment_gateway_account", "payment_account", "payment_account
cur_frm.add_fetch("payment_gateway_account", "payment_gateway", "payment_gateway")
cur_frm.add_fetch("payment_gateway_account", "message", "message")
+frappe.ui.form.on("Payment Request", {
+ setup: function(frm) {
+ frm.set_query("party_type", function() {
+ return {
+ query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
+ };
+ });
+ }
+})
+
frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){
if (frm.doc.reference_doctype) {
frappe.call({
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index eda59abf04e..6133b1ccd4b 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -350,13 +350,13 @@ def get_amount(ref_doc):
if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
- if dt in ["Sales Invoice", "Purchase Invoice"]:
+ elif dt in ["Sales Invoice", "Purchase Invoice"]:
if ref_doc.party_account_currency == ref_doc.currency:
grand_total = flt(ref_doc.outstanding_amount)
else:
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
- if dt == "Fees":
+ elif dt == "Fees":
grand_total = ref_doc.outstanding_amount
if grand_total > 0 :
diff --git a/erpnext/assets/doctype/asset_settings/__init__.py b/erpnext/accounts/doctype/pos_field/__init__.py
similarity index 100%
rename from erpnext/assets/doctype/asset_settings/__init__.py
rename to erpnext/accounts/doctype/pos_field/__init__.py
diff --git a/erpnext/accounts/doctype/pos_field/pos_field.json b/erpnext/accounts/doctype/pos_field/pos_field.json
new file mode 100644
index 00000000000..13edabd985d
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_field/pos_field.json
@@ -0,0 +1,77 @@
+{
+ "creation": "2019-08-22 14:35:39.043242",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "fieldname",
+ "label",
+ "fieldtype",
+ "column_break_7",
+ "options",
+ "default_value",
+ "reqd",
+ "read_only"
+ ],
+ "fields": [
+ {
+ "fieldname": "fieldname",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Fieldname"
+ },
+ {
+ "fieldname": "fieldtype",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Fieldtype",
+ "read_only": 1
+ },
+ {
+ "fieldname": "label",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Label",
+ "read_only": 1
+ },
+ {
+ "fieldname": "options",
+ "fieldtype": "Text",
+ "in_list_view": 1,
+ "label": "Options",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "reqd",
+ "fieldtype": "Check",
+ "label": "Mandatory"
+ },
+ {
+ "default": "0",
+ "fieldname": "read_only",
+ "fieldtype": "Check",
+ "label": "Read Only"
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "default_value",
+ "fieldtype": "Data",
+ "label": "Default Value"
+ }
+ ],
+ "istable": 1,
+ "modified": "2019-08-23 13:59:34.025523",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Field",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/setup_progress_action/setup_progress_action.py b/erpnext/accounts/doctype/pos_field/pos_field.py
similarity index 61%
rename from erpnext/setup/doctype/setup_progress_action/setup_progress_action.py
rename to erpnext/accounts/doctype/pos_field/pos_field.py
index 24af94347e5..b4720b309bd 100644
--- a/erpnext/setup/doctype/setup_progress_action/setup_progress_action.py
+++ b/erpnext/accounts/doctype/pos_field/pos_field.py
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
+# import frappe
from frappe.model.document import Document
-class SetupProgressAction(Document):
+class POSField(Document):
pass
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js
index 1a146185139..f5b681bd41d 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.js
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js
@@ -2,7 +2,46 @@
// For license information, please see license.txt
frappe.ui.form.on('POS Settings', {
- refresh: function() {
+ onload: function(frm) {
+ frm.trigger("get_invoice_fields");
+ },
+ use_pos_in_offline_mode: function(frm) {
+ frm.trigger("get_invoice_fields");
+ },
+
+ get_invoice_fields: function(frm) {
+ if (!frm.doc.use_pos_in_offline_mode) {
+ frappe.model.with_doctype("Sales Invoice", () => {
+ var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
+ if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
+ d.fieldtype === 'Table') {
+ return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
+ } else {
+ return null;
+ }
+ });
+
+ frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields);
+ });
+ } else {
+ frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""];
+ }
+ }
+});
+
+frappe.ui.form.on("POS Field", {
+ fieldname: function(frm, doctype, name) {
+ var doc = frappe.get_doc(doctype, name);
+ var df = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
+ return doc.fieldname == d.fieldname ? d : null;
+ })[0];
+
+ doc.label = df.label;
+ doc.reqd = df.reqd;
+ doc.options = df.options;
+ doc.fieldtype = df.fieldtype;
+ doc.default_value = df.default;
+ frm.refresh_field("fields");
}
});
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json
index 8f5b631c89a..1d55880415f 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.json
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json
@@ -1,133 +1,68 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-08-28 16:46:41.732676",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2017-08-28 16:46:41.732676",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "use_pos_in_offline_mode",
+ "section_break_2",
+ "fields"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fieldname": "use_pos_in_offline_mode",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Use POS in Offline Mode",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "default": "0",
+ "fieldname": "use_pos_in_offline_mode",
+ "fieldtype": "Check",
+ "label": "Use POS in Offline Mode"
+ },
+ {
+ "fieldname": "section_break_2",
+ "fieldtype": "Section Break"
+ },
+ {
+ "depends_on": "eval:!doc.use_pos_in_offline_mode",
+ "fieldname": "fields",
+ "fieldtype": "Table",
+ "label": "POS Field",
+ "options": "POS Field"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-09-11 13:57:28.787023",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "POS Settings",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "issingle": 1,
+ "links": [],
+ "modified": "2019-12-26 11:50:47.122997",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Settings",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Accounts User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Accounts User",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Sales User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Sales User",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index 971d308368a..29d83783d07 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -389,8 +389,7 @@
"fieldname": "rate_or_discount",
"fieldtype": "Select",
"label": "Rate or Discount",
- "options": "\nRate\nDiscount Percentage\nDiscount Amount",
- "reqd": 1
+ "options": "\nRate\nDiscount Percentage\nDiscount Amount"
},
{
"default": "Grand Total",
@@ -439,19 +438,20 @@
},
{
"default": "0",
- "depends_on": "eval:!doc.mixed_conditions",
+ "depends_on": "eval:!doc.mixed_conditions && doc.apply_on != 'Transaction'",
"fieldname": "same_item",
"fieldtype": "Check",
"label": "Same Item"
},
{
- "depends_on": "eval:!doc.same_item || doc.mixed_conditions",
+ "depends_on": "eval:(!doc.same_item || doc.apply_on == 'Transaction') || doc.mixed_conditions",
"fieldname": "free_item",
"fieldtype": "Link",
"label": "Free Item",
"options": "Item"
},
{
+ "default": "0",
"fieldname": "free_qty",
"fieldtype": "Float",
"label": "Qty"
@@ -554,7 +554,7 @@
],
"icon": "fa fa-gift",
"idx": 1,
- "modified": "2019-10-15 12:39:40.399792",
+ "modified": "2019-12-18 17:29:22.957077",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 17762755f42..3c14819e6fe 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -34,8 +34,7 @@ class PricingRule(Document):
def validate_duplicate_apply_on(self):
field = apply_on_dict.get(self.apply_on)
- values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field)]
-
+ values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
if len(values) != len(set(values)):
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
@@ -48,6 +47,9 @@ class PricingRule(Document):
if tocheck and not self.get(tocheck):
throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError)
+ if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
+ throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
+
def validate_applicable_for_selling_or_buying(self):
if not self.selling and not self.buying:
throw(_("Atleast one of the Selling or Buying must be selected"))
@@ -181,8 +183,9 @@ def get_serial_no_for_item(args):
item_details.serial_no = get_serial_no(args)
return item_details
-def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
- from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rules
+def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
+ from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules,
+ get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule)
if isinstance(doc, string_types):
doc = json.loads(doc)
@@ -209,6 +212,57 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
item_details, args.get('item_code'))
return item_details
+ update_args_for_pricing_rule(args)
+
+ pricing_rules = (get_applied_pricing_rules(args)
+ if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc))
+
+ if pricing_rules:
+ rules = []
+
+ for pricing_rule in pricing_rules:
+ if not pricing_rule: continue
+
+ if isinstance(pricing_rule, string_types):
+ pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
+ pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule)
+
+ if pricing_rule.get('suggestion'): continue
+
+ item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0)
+ item_details.price_or_product_discount = pricing_rule.get("price_or_product_discount")
+
+ rules.append(get_pricing_rule_details(args, pricing_rule))
+
+ if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
+ item_details.update({
+ 'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items),
+ 'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other)
+ if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
+ })
+
+ if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
+ return item_details
+
+ if not pricing_rule.validate_applied_rule:
+ if pricing_rule.price_or_product_discount == "Price":
+ apply_price_discount_rule(pricing_rule, item_details, args)
+ else:
+ get_product_discount_rule(pricing_rule, item_details, doc)
+
+ item_details.has_pricing_rule = 1
+
+ item_details.pricing_rules = ','.join([d.pricing_rule for d in rules])
+
+ if not doc: return item_details
+
+ elif args.get("pricing_rules"):
+ item_details = remove_pricing_rule_for_item(args.get("pricing_rules"),
+ item_details, args.get('item_code'))
+
+ return item_details
+
+def update_args_for_pricing_rule(args):
if not (args.item_group and args.brand):
try:
args.item_group, args.brand = frappe.get_cached_value("Item", args.item_code, ["item_group", "brand"])
@@ -235,56 +289,16 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
args.supplier_group = frappe.get_cached_value("Supplier", args.supplier, "supplier_group")
args.customer = args.customer_group = args.territory = None
- pricing_rules = get_pricing_rules(args, doc)
-
- if pricing_rules:
- rules = []
-
- for pricing_rule in pricing_rules:
- if not pricing_rule or pricing_rule.get('suggestion'): continue
-
- item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0)
-
- rules.append(get_pricing_rule_details(args, pricing_rule))
- if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
- continue
-
- if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
- return item_details
-
- if (not pricing_rule.validate_applied_rule and
- pricing_rule.price_or_product_discount == "Price"):
- apply_price_discount_pricing_rule(pricing_rule, item_details, args)
-
- item_details.has_pricing_rule = 1
-
- # if discount is applied on the rate and not on price list rate
- # if price_list_rate:
- # set_discount_amount(price_list_rate, item_details)
-
- item_details.pricing_rules = ','.join([d.pricing_rule for d in rules])
-
- if not doc: return item_details
-
- for rule in rules:
- doc.append('pricing_rules', rule)
-
- elif args.get("pricing_rules"):
- item_details = remove_pricing_rule_for_item(args.get("pricing_rules"),
- item_details, args.get('item_code'))
-
- return item_details
-
def get_pricing_rule_details(args, pricing_rule):
return frappe._dict({
'pricing_rule': pricing_rule.name,
'rate_or_discount': pricing_rule.rate_or_discount,
'margin_type': pricing_rule.margin_type,
- 'item_code': pricing_rule.item_code or args.get("item_code"),
+ 'item_code': args.get("item_code"),
'child_docname': args.get('child_docname')
})
-def apply_price_discount_pricing_rule(pricing_rule, item_details, args):
+def apply_price_discount_rule(pricing_rule, item_details, args):
item_details.pricing_rule_for = pricing_rule.rate_or_discount
if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency)
@@ -327,10 +341,10 @@ def set_discount_amount(rate, item_details):
item_details.rate = rate
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
- from erpnext.accounts.doctype.pricing_rule.utils import get_apply_on_and_items
+ from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items
for d in pricing_rules.split(','):
if not d or not frappe.db.exists("Pricing Rule", d): continue
- pricing_rule = frappe.get_doc('Pricing Rule', d)
+ pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
if pricing_rule.price_or_product_discount == 'Price':
if pricing_rule.rate_or_discount == 'Discount Percentage':
@@ -348,8 +362,9 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
else pricing_rule.get('free_item'))
if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"):
- apply_on, items = get_apply_on_and_items(pricing_rule, item_details)
- item_details.apply_on = apply_on
+ items = get_pricing_rule_items(pricing_rule)
+ item_details.apply_on = (frappe.scrub(pricing_rule.apply_rule_on_other)
+ if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
item_details.applied_on_items = ','.join(items)
item_details.pricing_rules = ''
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index ef26c2e7bfd..fe68fdb6c0f 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -7,7 +7,8 @@ from __future__ import unicode_literals
import frappe, copy, json
from frappe import throw, _
from six import string_types
-from frappe.utils import flt, cint, get_datetime
+from frappe.utils import flt, cint, get_datetime, get_link_to_form, today
+from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor
@@ -173,10 +174,11 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
if (field and pricing_rules[0].get('other_' + field) != args.get(field)): return
- pr_doc = frappe.get_doc('Pricing Rule', pricing_rules[0].name)
+ pr_doc = frappe.get_cached_doc('Pricing Rule', pricing_rules[0].name)
if pricing_rules[0].mixed_conditions and doc:
- stock_qty, amount = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args)
+ stock_qty, amount, items = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args)
+ pricing_rules[0].apply_rule_on_other_items = items
elif pricing_rules[0].is_cumulative:
items = [args.get(frappe.scrub(pr_doc.get('apply_on')))]
@@ -282,7 +284,7 @@ def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None):
status = True
# if user has created item price against the transaction UOM
- if rule.get("uom") == args.get("uom"):
+ if args and rule.get("uom") == args.get("uom"):
conversion_factor = 1.0
if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor)
@@ -339,17 +341,19 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
sum_qty += data[0]
sum_amt += data[1]
- return sum_qty, sum_amt
+ return sum_qty, sum_amt, items
def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules):
- for d in get_pricing_rule_items(pr_doc):
- for row in doc.items:
- if d == row.get(frappe.scrub(pr_doc.apply_on)):
- pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
- row.get("amount"), pricing_rules, row)
+ items = get_pricing_rule_items(pr_doc)
- if pricing_rules and pricing_rules[0]:
- return pricing_rules
+ for row in doc.items:
+ if row.get(frappe.scrub(pr_doc.apply_rule_on_other)) in items:
+ pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
+ row.get("amount"), pricing_rules, row)
+
+ if pricing_rules and pricing_rules[0]:
+ pricing_rules[0].apply_rule_on_other_items = items
+ return pricing_rules
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
sum_qty, sum_amt = [0, 0]
@@ -397,38 +401,15 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
return [sum_qty, sum_amt]
-def validate_pricing_rules(doc):
- validate_pricing_rule_on_transactions(doc)
-
- for d in doc.items:
- validate_pricing_rule_on_items(doc, d)
-
- doc.calculate_taxes_and_totals()
-
-def validate_pricing_rule_on_items(doc, item_row, do_not_validate = False):
- value = 0
- for pricing_rule in get_applied_pricing_rules(doc, item_row):
- pr_doc = frappe.get_doc('Pricing Rule', pricing_rule)
-
- if pr_doc.get('apply_on') == 'Transaction': continue
-
- if pr_doc.get('price_or_product_discount') == 'Product':
- apply_pricing_rule_for_free_items(doc, pr_doc)
- else:
- for field in ['discount_percentage', 'discount_amount', 'rate']:
- if not pr_doc.get(field): continue
-
- value += pr_doc.get(field)
- apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate)
-
-def validate_pricing_rule_on_transactions(doc):
+def apply_pricing_rule_on_transaction(doc):
conditions = "apply_on = 'Transaction'"
values = {}
conditions = get_other_conditions(conditions, values, doc)
pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule`
- where {conditions} """.format(conditions = conditions), values, as_dict=1)
+ where {conditions} and `tabPricing Rule`.disable = 0
+ """.format(conditions = conditions), values, as_dict=1)
if pricing_rules:
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
@@ -440,98 +421,83 @@ def validate_pricing_rule_on_transactions(doc):
doc.set('apply_discount_on', d.apply_discount_on)
for field in ['additional_discount_percentage', 'discount_amount']:
- if not d.get(field): continue
-
pr_field = ('discount_percentage'
if field == 'additional_discount_percentage' else field)
+ if not d.get(pr_field): continue
+
if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
frappe.msgprint(_("User has not applied rule on the invoice {0}")
.format(doc.name))
else:
doc.set(field, d.get(pr_field))
- elif d.price_or_product_discount == 'Product':
- apply_pricing_rule_for_free_items(doc, d)
-def get_applied_pricing_rules(doc, item_row):
+ doc.calculate_taxes_and_totals()
+ elif d.price_or_product_discount == 'Product':
+ item_details = frappe._dict({'parenttype': doc.doctype})
+ get_product_discount_rule(d, item_details, doc)
+ apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
+ doc.set_missing_values()
+
+def get_applied_pricing_rules(item_row):
return (item_row.get("pricing_rules").split(',')
if item_row.get("pricing_rules") else [])
-def apply_pricing_rule_for_free_items(doc, pricing_rule):
- if pricing_rule.get('free_item'):
+def get_product_discount_rule(pricing_rule, item_details, doc=None):
+ free_item = (pricing_rule.free_item
+ if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code)
+
+ if not free_item:
+ frappe.throw(_("Free item not set in the pricing rule {0}")
+ .format(get_link_to_form("Pricing Rule", pricing_rule.name)))
+
+ item_details.free_item_data = {
+ 'item_code': free_item,
+ 'qty': pricing_rule.free_qty or 1,
+ 'rate': pricing_rule.free_item_rate or 0,
+ 'price_list_rate': pricing_rule.free_item_rate or 0,
+ 'is_free_item': 1
+ }
+
+ item_data = frappe.get_cached_value('Item', free_item, ['item_name',
+ 'description', 'stock_uom'], as_dict=1)
+
+ item_details.free_item_data.update(item_data)
+ item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
+ item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item,
+ item_details.free_item_data['uom']).get("conversion_factor", 1)
+
+ if item_details.get("parenttype") == 'Purchase Order':
+ item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today()
+
+ if item_details.get("parenttype") == 'Sales Order':
+ item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
+
+def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
+ if pricing_rule_args.get('item_code'):
items = [d.item_code for d in doc.items
- if d.item_code == (d.item_code
- if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item]
+ if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item]
if not items:
- doc.append('items', {
- 'item_code': pricing_rule.get('free_item'),
- 'qty': pricing_rule.get('free_qty'),
- 'uom': pricing_rule.get('free_item_uom'),
- 'rate': pricing_rule.get('free_item_rate'),
- 'is_free_item': 1
- })
-
- doc.set_missing_values()
-
-def apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate=False):
- apply_on, items = get_apply_on_and_items(pr_doc, item_row)
-
- rule_applied = {}
-
- for item in doc.get("items"):
- if item.get(apply_on) in items:
- if not item.pricing_rules:
- item.pricing_rules = item_row.pricing_rules
-
- for field in ['discount_percentage', 'discount_amount', 'rate']:
- if not pr_doc.get(field): continue
-
- key = (item.name, item.pricing_rules)
- if not pr_doc.validate_applied_rule:
- rule_applied[key] = 1
- item.set(field, value)
- elif item.get(field) < value:
- if not do_not_validate and item.idx == item_row.idx:
- rule_applied[key] = 0
- frappe.msgprint(_("Row {0}: user has not applied rule {1} on the item {2}")
- .format(item.idx, pr_doc.title, item.item_code))
-
- if rule_applied and doc.get("pricing_rules"):
- for d in doc.get("pricing_rules"):
- key = (d.child_docname, d.pricing_rule)
- if key in rule_applied:
- d.rule_applied = 1
-
-def get_apply_on_and_items(pr_doc, item_row):
- # for mixed or other items conditions
- apply_on = frappe.scrub(pr_doc.get('apply_on'))
- items = (get_pricing_rule_items(pr_doc)
- if pr_doc.mixed_conditions else [item_row.get(apply_on)])
-
- if pr_doc.apply_rule_on_other:
- apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
- items = [pr_doc.get(apply_on)]
-
- return apply_on, items
+ doc.append('items', pricing_rule_args)
def get_pricing_rule_items(pr_doc):
+ apply_on_data = []
apply_on = frappe.scrub(pr_doc.get('apply_on'))
pricing_rule_apply_on = apply_on_table.get(pr_doc.get('apply_on'))
- return [item.get(apply_on) for item in pr_doc.get(pricing_rule_apply_on)] or []
+ for d in pr_doc.get(pricing_rule_apply_on):
+ if apply_on == 'item_group':
+ get_child_item_groups(d.get(apply_on))
+ else:
+ apply_on_data.append(d.get(apply_on))
-@frappe.whitelist()
-def validate_pricing_rule_for_different_cond(doc):
- if isinstance(doc, string_types):
- doc = json.loads(doc)
+ if pr_doc.apply_rule_on_other:
+ apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
+ apply_on_data.append(pr_doc.get("other_" + apply_on))
- doc = frappe.get_doc(doc)
- for d in doc.get("items"):
- validate_pricing_rule_on_items(doc, d, True)
-
- return doc
+ return list(set(apply_on_data))
def validate_coupon_code(coupon_name):
from frappe.utils import today,getdate
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index f4b656d3f68..643de7d300a 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -330,23 +330,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
frm: cur_frm
})
},
-
- asset: function(frm, cdt, cdn) {
- var row = locals[cdt][cdn];
- if(row.asset) {
- frappe.call({
- method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account",
- args: {
- "asset": row.asset,
- "fieldname": "fixed_asset_account",
- "account": row.expense_account
- },
- callback: function(r, rt) {
- frappe.model.set_value(cdt, cdn, "expense_account", r.message);
- }
- })
- }
- }
});
cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice);
@@ -399,21 +382,11 @@ cur_frm.fields_dict['items'].grid.get_field("item_code").get_query = function(do
cur_frm.fields_dict['credit_to'].get_query = function(doc) {
// filter on Account
- if (doc.supplier) {
- return {
- filters: {
- 'account_type': 'Payable',
- 'is_group': 0,
- 'company': doc.company
- }
- }
- } else {
- return {
- filters: {
- 'report_type': 'Balance Sheet',
- 'is_group': 0,
- 'company': doc.company
- }
+ return {
+ filters: {
+ 'account_type': 'Payable',
+ 'is_group': 0,
+ 'company': doc.company
}
}
}
@@ -430,19 +403,7 @@ cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn)
cur_frm.set_query("expense_account", "items", function(doc) {
return {
query: "erpnext.controllers.queries.get_expense_account",
- filters: {'company': doc.company}
- }
-});
-
-cur_frm.set_query("asset", "items", function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- return {
- filters: {
- 'item_code': d.item_code,
- 'docstatus': 1,
- 'company': doc.company,
- 'status': 'Submitted'
- }
+ filters: {'company': doc.company }
}
});
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 6fe18115c04..7725994c6b0 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:39",
@@ -417,6 +418,7 @@
"fieldname": "contact_email",
"fieldtype": "Small Text",
"label": "Contact Email",
+ "options": "Email",
"print_hide": 1,
"read_only": 1
},
@@ -705,7 +707,7 @@
},
{
"fieldname": "other_charges_calculation",
- "fieldtype": "Text",
+ "fieldtype": "Long Text",
"label": "Taxes and Charges Calculation",
"no_copy": 1,
"oldfieldtype": "HTML",
@@ -1287,7 +1289,8 @@
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
- "modified": "2019-09-17 22:31:42.666601",
+ "links": [],
+ "modified": "2019-12-30 19:13:49.610538",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 4ea9b1c6c97..917acba92c9 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -18,13 +18,14 @@ from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entri
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
-from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled
+from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
from frappe.model.mapper import get_mapped_doc
from six import iteritems
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
unlink_inter_company_doc
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from erpnext.accounts.deferred_revenue import validate_service_stop_date
+from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@@ -97,7 +98,6 @@ class PurchaseInvoice(BuyingController):
self.set_against_expense_account()
self.validate_write_off_account()
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items")
- self.validate_fixed_asset()
self.create_remarks()
self.set_status()
self.validate_purchase_receipt_if_update_stock()
@@ -225,6 +225,8 @@ class PurchaseInvoice(BuyingController):
# in case of auto inventory accounting,
# expense account is always "Stock Received But Not Billed" for a stock item
# except epening entry, drop-ship entry and fixed asset items
+ if item.item_code:
+ asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
if auto_accounting_for_stock and item.item_code in stock_items \
and self.is_opening == 'No' and not item.is_fixed_asset \
@@ -235,12 +237,8 @@ class PurchaseInvoice(BuyingController):
item.expense_account = warehouse_account[item.warehouse]["account"]
else:
item.expense_account = stock_not_billed_account
- elif item.is_fixed_asset and is_cwip_accounting_disabled():
- if not item.asset:
- frappe.throw(_("Row {0}: asset is required for item {1}")
- .format(item.idx, item.item_code))
-
- item.expense_account = get_asset_category_account(item.asset, 'fixed_asset_account',
+ elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
+ item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
company = self.company)
elif item.is_fixed_asset and item.pr_detail:
item.expense_account = asset_received_but_not_billed
@@ -250,7 +248,7 @@ class PurchaseInvoice(BuyingController):
def set_against_expense_account(self):
against_accounts = []
for item in self.get("items"):
- if item.expense_account not in against_accounts:
+ if item.expense_account and (item.expense_account not in against_accounts):
against_accounts.append(item.expense_account)
self.against_expense_account = ",".join(against_accounts)
@@ -391,7 +389,8 @@ class PurchaseInvoice(BuyingController):
self.make_supplier_gl_entry(gl_entries)
self.make_item_gl_entries(gl_entries)
- if not is_cwip_accounting_disabled():
+
+ if self.check_asset_cwip_enabled():
self.get_asset_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
@@ -404,6 +403,15 @@ class PurchaseInvoice(BuyingController):
return gl_entries
+ def check_asset_cwip_enabled(self):
+ # Check if there exists any item with cwip accounting enabled in it's asset category
+ for item in self.get("items"):
+ if item.item_code and item.is_fixed_asset:
+ asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
+ if is_cwip_accounting_enabled(asset_category):
+ return 1
+ return 0
+
def make_supplier_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total
@@ -436,15 +444,23 @@ class PurchaseInvoice(BuyingController):
if self.update_stock and self.auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company)
+ landed_cost_entries = get_item_account_wise_additional_cost(self.name)
+
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)
+ valuation_tax_accounts = [d.account_head for d in self.get("taxes")
+ if d.category in ('Valuation', 'Total and Valuation')
+ and flt(d.base_tax_amount_after_discount_amount)]
+
for item in self.get("items"):
if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account)
+ if item.item_code:
+ asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
# warehouse account
@@ -463,15 +479,16 @@ class PurchaseInvoice(BuyingController):
)
# Amount added through landed-cost-voucher
- if flt(item.landed_cost_voucher_amount):
- gl_entries.append(self.get_gl_dict({
- "account": expenses_included_in_valuation,
- "against": item.expense_account,
- "cost_center": item.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": flt(item.landed_cost_voucher_amount),
- "project": item.project
- }, item=item))
+ if landed_cost_entries:
+ for account, amount in iteritems(landed_cost_entries[(item.item_code, item.name)]):
+ gl_entries.append(self.get_gl_dict({
+ "account": account,
+ "against": item.expense_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(amount),
+ "project": item.project
+ }, item=item))
# sub-contracting warehouse
if flt(item.rm_supp_cost):
@@ -486,31 +503,61 @@ class PurchaseInvoice(BuyingController):
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.rm_supp_cost)
}, warehouse_account[self.supplier_warehouse]["account_currency"], item=item))
- elif not item.is_fixed_asset or (item.is_fixed_asset and is_cwip_accounting_disabled()):
+ elif not item.is_fixed_asset or (item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)):
expense_account = (item.expense_account
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
- gl_entries.append(
- self.get_gl_dict({
+ if not item.is_fixed_asset:
+ amount = flt(item.base_net_amount, item.precision("base_net_amount"))
+ else:
+ amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
+
+ gl_entries.append(self.get_gl_dict({
"account": expense_account,
"against": self.supplier,
- "debit": flt(item.base_net_amount, item.precision("base_net_amount")),
- "debit_in_account_currency": (flt(item.base_net_amount,
- item.precision("base_net_amount")) if account_currency==self.company_currency
- else flt(item.net_amount, item.precision("net_amount"))),
+ "debit": amount,
"cost_center": item.cost_center,
"project": item.project
- }, account_currency, item=item)
- )
+ }, 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:
+ expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
+ # Amount added through landed-cost-voucher
+ gl_entries.append(self.get_gl_dict({
+ "account": expenses_included_in_asset_valuation,
+ "against": expense_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(item.landed_cost_voucher_amount),
+ "project": item.project
+ }, item=item))
+
+ gl_entries.append(self.get_gl_dict({
+ "account": expense_account,
+ "against": expenses_included_in_asset_valuation,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "debit": flt(item.landed_cost_voucher_amount),
+ "project": item.project
+ }, item=item))
+
+ # update gross amount of asset bought through this document
+ assets = frappe.db.get_all('Asset',
+ filters={ 'purchase_invoice': self.name, 'item_code': item.item_code }
+ )
+ for asset in assets:
+ frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
+ frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
if self.auto_accounting_for_stock and self.is_opening == "No" and \
item.item_code in stock_items and item.item_tax_amount:
# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
- if item.purchase_receipt:
+ if item.purchase_receipt and valuation_tax_accounts:
negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry`
- where voucher_type='Purchase Receipt' and voucher_no=%s and account=%s""",
- (item.purchase_receipt, self.expenses_included_in_valuation))
+ where voucher_type='Purchase Receipt' and voucher_no=%s and account in %s""",
+ (item.purchase_receipt, valuation_tax_accounts))
if not negative_expense_booked_in_pr:
gl_entries.append(
@@ -527,27 +574,27 @@ class PurchaseInvoice(BuyingController):
item.precision("item_tax_amount"))
def get_asset_gl_entry(self, gl_entries):
+ arbnb_account = self.get_company_default("asset_received_but_not_billed")
+ eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
+
for item in self.get("items"):
if item.is_fixed_asset:
- eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
-
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate)
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
- if (not item.expense_account or frappe.db.get_value('Account',
- item.expense_account, 'account_type') not in ['Asset Received But Not Billed', 'Fixed Asset']):
- arbnb_account = self.get_company_default("asset_received_but_not_billed")
+ item_exp_acc_type = frappe.db.get_value('Account', item.expense_account, 'account_type')
+ if (not item.expense_account or item_exp_acc_type not in ['Asset Received But Not Billed', 'Fixed Asset']):
item.expense_account = arbnb_account
if not self.update_stock:
- asset_rbnb_currency = get_account_currency(item.expense_account)
+ arbnb_currency = get_account_currency(item.expense_account)
gl_entries.append(self.get_gl_dict({
"account": item.expense_account,
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": base_asset_amount,
"debit_in_account_currency": (base_asset_amount
- if asset_rbnb_currency == self.company_currency else asset_amount),
+ if arbnb_currency == self.company_currency else asset_amount),
"cost_center": item.cost_center
}, item=item))
@@ -564,8 +611,7 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount / self.conversion_rate)
}, item=item))
else:
- cwip_account = get_asset_account("capital_work_in_progress_account",
- item.asset, company = self.company)
+ cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
cwip_account_currency = get_account_currency(cwip_account)
gl_entries.append(self.get_gl_dict({
@@ -591,6 +637,36 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount / self.conversion_rate)
}, item=item))
+ # When update stock is checked
+ # Assets are bought through this document then it will be linked to this document
+ if self.update_stock:
+ if flt(item.landed_cost_voucher_amount):
+ gl_entries.append(self.get_gl_dict({
+ "account": eiiav_account,
+ "against": cwip_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": flt(item.landed_cost_voucher_amount),
+ "project": item.project
+ }, item=item))
+
+ gl_entries.append(self.get_gl_dict({
+ "account": cwip_account,
+ "against": eiiav_account,
+ "cost_center": item.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "debit": flt(item.landed_cost_voucher_amount),
+ "project": item.project
+ }, item=item))
+
+ # update gross amount of assets bought through this document
+ assets = frappe.db.get_all('Asset',
+ filters={ 'purchase_invoice': self.name, 'item_code': item.item_code }
+ )
+ for asset in assets:
+ frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
+ frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
+
return gl_entries
def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
@@ -641,14 +717,14 @@ class PurchaseInvoice(BuyingController):
if account_currency==self.company_currency \
else tax.tax_amount_after_discount_amount,
"cost_center": tax.cost_center
- }, account_currency)
+ }, account_currency, item=tax)
)
# accumulate valuation tax
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
if self.auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
- valuation_tax.setdefault(tax.cost_center, 0)
- valuation_tax[tax.cost_center] += \
+ valuation_tax.setdefault(tax.name, 0)
+ valuation_tax[tax.name] += \
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount)
if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax:
@@ -658,36 +734,38 @@ class PurchaseInvoice(BuyingController):
total_valuation_amount = sum(valuation_tax.values())
amount_including_divisional_loss = self.negative_expense_to_be_booked
i = 1
- for cost_center, amount in iteritems(valuation_tax):
- if i == len(valuation_tax):
- applicable_amount = amount_including_divisional_loss
- else:
- applicable_amount = self.negative_expense_to_be_booked * (amount / total_valuation_amount)
- amount_including_divisional_loss -= applicable_amount
+ for tax in self.get("taxes"):
+ if valuation_tax.get(tax.name):
+ if i == len(valuation_tax):
+ applicable_amount = amount_including_divisional_loss
+ else:
+ applicable_amount = self.negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
+ amount_including_divisional_loss -= applicable_amount
- gl_entries.append(
- self.get_gl_dict({
- "account": self.expenses_included_in_valuation,
- "cost_center": cost_center,
- "against": self.supplier,
- "credit": applicable_amount,
- "remarks": self.remarks or "Accounting Entry for Stock"
- })
- )
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": tax.account_head,
+ "cost_center": tax.cost_center,
+ "against": self.supplier,
+ "credit": applicable_amount,
+ "remarks": self.remarks or _("Accounting Entry for Stock"),
+ }, item=tax)
+ )
- i += 1
+ i += 1
if self.auto_accounting_for_stock and self.update_stock and valuation_tax:
- for cost_center, amount in iteritems(valuation_tax):
- gl_entries.append(
- self.get_gl_dict({
- "account": self.expenses_included_in_valuation,
- "cost_center": cost_center,
- "against": self.supplier,
- "credit": amount,
- "remarks": self.remarks or "Accounting Entry for Stock"
- })
- )
+ for tax in self.get("taxes"):
+ if valuation_tax.get(tax.name):
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": tax.account_head,
+ "cost_center": tax.cost_center,
+ "against": self.supplier,
+ "credit": valuation_tax[tax.name],
+ "remarks": self.remarks or "Accounting Entry for Stock"
+ }, item=tax)
+ )
def make_payment_gl_entries(self, gl_entries):
# Make Cash GL Entries
@@ -752,7 +830,11 @@ class PurchaseInvoice(BuyingController):
)
def make_gle_for_rounding_adjustment(self, gl_entries):
- if self.rounding_adjustment:
+ # if rounding adjustment in small and conversion rate is also small then
+ # base_rounding_adjustment may become zero due to small precision
+ # eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2
+ # then base_rounding_adjustment becomes zero and error is thrown in GL Entry
+ if self.rounding_adjustment and self.base_rounding_adjustment:
round_off_account, round_off_cost_center = \
get_round_off_account_and_cost_center(self.company)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
index 4e76a8d9552..800ed921bdf 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
@@ -6,8 +6,8 @@ frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return", "release_date", "on_hold"],
get_indicator: function(doc) {
- if(flt(doc.outstanding_amount) < 0 && doc.docstatus == 1) {
- return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<,0"]
+ if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
+ return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "darkgrey"];
diff --git a/erpnext/accounts/doctype/purchase_invoice/regional/india.js b/erpnext/accounts/doctype/purchase_invoice/regional/india.js
new file mode 100644
index 00000000000..81488a2c52a
--- /dev/null
+++ b/erpnext/accounts/doctype/purchase_invoice/regional/india.js
@@ -0,0 +1,3 @@
+{% include "erpnext/regional/india/taxes.js" %}
+
+erpnext.setup_auto_gst_taxation('Purchase Invoice');
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 6deee381481..e41ad428469 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -10,7 +10,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_ent
from frappe.utils import cint, flt, today, nowdate, add_days
import frappe.defaults
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
- test_records as pr_test_records
+ test_records as pr_test_records, make_purchase_receipt, get_taxes
from erpnext.controllers.accounts_controller import get_payment_terms
from erpnext.exceptions import InvalidCurrency
from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
@@ -57,16 +57,11 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account))
def test_gl_entries_with_perpetual_inventory(self):
- pi = frappe.copy_doc(test_records[1])
- set_perpetual_inventory(1, pi.company)
+ pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10)
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
- pi.insert()
- pi.submit()
self.check_gle_for_pi(pi.name)
- set_perpetual_inventory(0, pi.company)
-
def test_terms_added_after_save(self):
pi = frappe.copy_doc(test_records[1])
pi.insert()
@@ -196,32 +191,33 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(pi.on_hold, 0)
def test_gl_entries_with_perpetual_inventory_against_pr(self):
- pr = frappe.copy_doc(pr_test_records[0])
- set_perpetual_inventory(1, pr.company)
- self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
- pr.submit()
- pi = frappe.copy_doc(test_records[1])
- for d in pi.get("items"):
+ pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,)
+
+ self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
+
+ pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True")
+
+ for d in pi.items:
d.purchase_receipt = pr.name
+
pi.insert()
pi.submit()
self.check_gle_for_pi(pi.name)
- set_perpetual_inventory(0, pr.company)
-
def check_gle_for_pi(self, pi):
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql("""select account, sum(debit) as debit, sum(credit) as credit
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
- order by account asc""", pi, as_dict=1)
+ group by account""", pi, as_dict=1)
+
self.assertTrue(gl_entries)
expected_values = dict((d[0], d) for d in [
- ["_Test Payable - _TC", 0, 720],
- ["Stock Received But Not Billed - _TC", 500.0, 0],
- ["_Test Account Shipping Charges - _TC", 100.0, 0],
- ["_Test Account VAT - _TC", 120.0, 0],
+ ["Creditors - TCP1", 0, 720],
+ ["Stock Received But Not Billed - TCP1", 500.0, 0],
+ ["_Test Account Shipping Charges - TCP1", 100.0, 0.0],
+ ["_Test Account VAT - TCP1", 120.0, 0]
])
for i, gle in enumerate(gl_entries):
@@ -524,10 +520,9 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertFalse(gle)
def test_purchase_invoice_update_stock_gl_entry_with_perpetual_inventory(self):
- set_perpetual_inventory()
pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(),
- posting_time=frappe.utils.nowtime())
+ posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
@@ -548,9 +543,9 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_gl_entries[gle.account][2], gle.credit)
def test_purchase_invoice_for_is_paid_and_update_stock_gl_entry_with_perpetual_inventory(self):
- set_perpetual_inventory()
+
pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(),
- posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - _TC", is_paid=1)
+ posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1", is_paid=1, company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
gl_entries = frappe.db.sql("""select account, account_currency, sum(debit) as debit,
sum(credit) as credit, debit_in_account_currency, credit_in_account_currency
@@ -563,7 +558,7 @@ class TestPurchaseInvoice(unittest.TestCase):
expected_gl_entries = dict((d[0], d) for d in [
[pi.credit_to, 250.0, 250.0],
[stock_in_hand_account, 250.0, 0.0],
- ["Cash - _TC", 0.0, 250.0]
+ ["Cash - TCP1", 0.0, 250.0]
])
for i, gle in enumerate(gl_entries):
@@ -630,6 +625,7 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(pi.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
def test_rejected_serial_no(self):
+ set_perpetual_inventory(0)
pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
rejected_qty=1, rate=500, update_stock=1,
rejected_warehouse = "_Test Rejected Warehouse - _TC")
@@ -881,7 +877,7 @@ def make_purchase_invoice(**args):
pi.is_return = args.is_return
pi.return_against = args.return_against
pi.is_subcontracted = args.is_subcontracted or "No"
- pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
+ pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
pi.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
@@ -890,14 +886,21 @@ def make_purchase_invoice(**args):
"received_qty": args.received_qty or 0,
"rejected_qty": args.rejected_qty or 0,
"rate": args.rate or 50,
+ 'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC',
"conversion_factor": 1.0,
"serial_no": args.serial_no,
"stock_uom": "_Test UOM",
- "cost_center": "_Test Cost Center - _TC",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
"project": args.project,
"rejected_warehouse": args.rejected_warehouse or "",
"rejected_serial_no": args.rejected_serial_no or ""
})
+
+ if args.get_taxes_and_charges:
+ taxes = get_taxes()
+ for tax in taxes:
+ pi.append("taxes", tax)
+
if not args.do_not_save:
pi.insert()
if not args.do_not_submit:
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 3a19bb1b6bf..acb0398b5c0 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2013-05-22 12:43:10",
"doctype": "DocType",
@@ -71,8 +72,8 @@
"expense_account",
"col_break5",
"is_fixed_asset",
- "asset",
"asset_location",
+ "asset_category",
"deferred_expense_section",
"deferred_expense_account",
"service_stop_date",
@@ -116,6 +117,8 @@
"fieldtype": "Column Break"
},
{
+ "fetch_from": "item_code.item_name",
+ "fetch_if_empty": 1,
"fieldname": "item_name",
"fieldtype": "Data",
"in_global_search": 1,
@@ -414,6 +417,7 @@
"print_hide": 1
},
{
+ "depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
@@ -425,12 +429,14 @@
"fieldtype": "Column Break"
},
{
+ "depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"no_copy": 1
},
{
+ "depends_on": "eval:!doc.is_fixed_asset",
"fieldname": "rejected_serial_no",
"fieldtype": "Text",
"label": "Rejected Serial No",
@@ -502,7 +508,8 @@
"depends_on": "enable_deferred_expense",
"fieldname": "service_stop_date",
"fieldtype": "Date",
- "label": "Service Stop Date"
+ "label": "Service Stop Date",
+ "no_copy": 1
},
{
"default": "0",
@@ -518,13 +525,15 @@
"depends_on": "enable_deferred_expense",
"fieldname": "service_start_date",
"fieldtype": "Date",
- "label": "Service Start Date"
+ "label": "Service Start Date",
+ "no_copy": 1
},
{
"depends_on": "enable_deferred_expense",
"fieldname": "service_end_date",
"fieldtype": "Date",
- "label": "Service End Date"
+ "label": "Service End Date",
+ "no_copy": 1
},
{
"fieldname": "reference",
@@ -615,6 +624,7 @@
},
{
"default": "0",
+ "fetch_from": "item_code.is_fixed_asset",
"fieldname": "is_fixed_asset",
"fieldtype": "Check",
"hidden": 1,
@@ -623,14 +633,6 @@
"print_hide": 1,
"read_only": 1
},
- {
- "depends_on": "is_fixed_asset",
- "fieldname": "asset",
- "fieldtype": "Link",
- "label": "Asset",
- "no_copy": 1,
- "options": "Asset"
- },
{
"depends_on": "is_fixed_asset",
"fieldname": "asset_location",
@@ -676,7 +678,7 @@
"fieldname": "pr_detail",
"fieldtype": "Data",
"hidden": 1,
- "label": "PR Detail",
+ "label": "Purchase Receipt Detail",
"no_copy": 1,
"oldfieldname": "pr_detail",
"oldfieldtype": "Data",
@@ -754,11 +756,22 @@
"fieldtype": "Data",
"label": "Manufacturer Part Number",
"read_only": 1
+ },
+ {
+ "depends_on": "is_fixed_asset",
+ "fetch_from": "item_code.asset_category",
+ "fieldname": "asset_category",
+ "fieldtype": "Data",
+ "in_preview": 1,
+ "label": "Asset Category",
+ "options": "Asset Category",
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-09-17 22:32:05.984240",
+ "links": [],
+ "modified": "2019-12-04 12:23:17.046413",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
index bc42630d474..a18fec61cf3 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
@@ -1,300 +1,108 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:title",
- "beta": 0,
- "creation": "2013-01-10 16:34:08",
- "custom": 0,
- "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "creation": "2013-01-10 16:34:08",
+ "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "field_order": [
+ "title",
+ "is_default",
+ "disabled",
+ "column_break4",
+ "company",
+ "tax_category",
+ "section_break6",
+ "taxes"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "title",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "label": "Title",
+ "no_copy": 1,
+ "oldfieldname": "title",
+ "oldfieldtype": "Data",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "is_default",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Default",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "is_default",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Default"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "disabled",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Disabled",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Disabled"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break4",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break4",
+ "fieldtype": "Column Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Company",
+ "options": "Company",
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break6",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "section_break6",
+ "fieldtype": "Section Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "taxes",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Purchase Taxes and Charges",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "purchase_tax_details",
- "oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "taxes",
+ "fieldtype": "Table",
+ "label": "Purchase Taxes and Charges",
+ "oldfieldname": "purchase_tax_details",
+ "oldfieldtype": "Table",
+ "options": "Purchase Taxes and Charges"
+ },
+ {
+ "fieldname": "tax_category",
+ "fieldtype": "Link",
+ "label": "Tax Category",
+ "options": "Tax Category"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-money",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-11-07 05:18:44.095798",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Purchase Taxes and Charges Template",
- "owner": "wasim@webnotestech.com",
+ ],
+ "icon": "fa fa-money",
+ "idx": 1,
+ "modified": "2019-11-25 13:05:26.220275",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Purchase Taxes and Charges Template",
+ "owner": "wasim@webnotestech.com",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Purchase Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Purchase Manager"
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Purchase Master Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Purchase Master Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "Purchase User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "read": 1,
+ "role": "Purchase User"
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_order": "DESC",
- "track_seen": 0
+ ],
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py
index 7d4fc639554..a48d2244893 100755
--- a/erpnext/accounts/doctype/sales_invoice/pos.py
+++ b/erpnext/accounts/doctype/sales_invoice/pos.py
@@ -357,14 +357,11 @@ def get_customer_wise_price_list():
def get_bin_data(pos_profile):
itemwise_bin_data = {}
- cond = "1=1"
+ filters = { 'actual_qty': ['>', 0] }
if pos_profile.get('warehouse'):
- cond = "warehouse = %(warehouse)s"
+ filters.update({ 'warehouse': pos_profile.get('warehouse') })
- bin_data = frappe.db.sql(""" select item_code, warehouse, actual_qty from `tabBin`
- where actual_qty > 0 and {cond}""".format(cond=cond), {
- 'warehouse': frappe.db.escape(pos_profile.get('warehouse'))
- }, as_dict=1)
+ bin_data = frappe.db.get_all('Bin', fields = ['item_code', 'warehouse', 'actual_qty'], filters=filters)
for bins in bin_data:
if bins.item_code not in itemwise_bin_data:
@@ -402,14 +399,21 @@ def make_invoice(doc_list={}, email_queue_list={}, customers_list={}):
for docs in doc_list:
for name, doc in iteritems(docs):
if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):
- validate_records(doc)
- si_doc = frappe.new_doc('Sales Invoice')
- si_doc.offline_pos_name = name
- si_doc.update(doc)
- si_doc.set_posting_time = 1
- si_doc.customer = get_customer_id(doc)
- si_doc.due_date = doc.get('posting_date')
- name_list = submit_invoice(si_doc, name, doc, name_list)
+ if isinstance(doc, dict):
+ validate_records(doc)
+ si_doc = frappe.new_doc('Sales Invoice')
+ si_doc.offline_pos_name = name
+ si_doc.update(doc)
+ si_doc.set_posting_time = 1
+ si_doc.customer = get_customer_id(doc)
+ si_doc.due_date = doc.get('posting_date')
+ name_list = submit_invoice(si_doc, name, doc, name_list)
+ else:
+ doc.due_date = doc.get('posting_date')
+ doc.customer = get_customer_id(doc)
+ doc.set_posting_time = 1
+ doc.offline_pos_name = name
+ name_list = submit_invoice(doc, name, doc, name_list)
else:
name_list.append(name)
@@ -543,11 +547,15 @@ def make_address(args, customer):
def make_email_queue(email_queue):
name_list = []
+
for key, data in iteritems(email_queue):
name = frappe.db.get_value('Sales Invoice', {'offline_pos_name': key}, 'name')
+ if not name: continue
+
data = json.loads(data)
sender = frappe.session.user
print_format = "POS Invoice" if not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')) else None
+
attachments = [frappe.attach_print('Sales Invoice', name, print_format=print_format)]
make(subject=data.get('subject'), content=data.get('content'), recipients=data.get('recipients'),
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js
index c8305e325f6..48fa364faf0 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/india.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js
@@ -1,3 +1,7 @@
+{% include "erpnext/regional/india/taxes.js" %}
+
+erpnext.setup_auto_gst_taxation('Sales Invoice');
+
frappe.ui.form.on("Sales Invoice", {
setup: function(frm) {
frm.set_query('transporter', function() {
@@ -35,4 +39,5 @@ frappe.ui.form.on("Sales Invoice", {
}, __("Make"));
}
}
+
});
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 3c852106635..db6ac55a119 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -556,22 +556,11 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn) {
}
cur_frm.set_query("debit_to", function(doc) {
- // filter on Account
- if (doc.customer) {
- return {
- filters: {
- 'account_type': 'Receivable',
- 'is_group': 0,
- 'company': doc.company
- }
- }
- } else {
- return {
- filters: {
- 'report_type': 'Balance Sheet',
- 'is_group': 0,
- 'company': doc.company
- }
+ return {
+ filters: {
+ 'account_type': 'Receivable',
+ 'is_group': 0,
+ 'company': doc.company
}
}
});
@@ -697,8 +686,8 @@ frappe.ui.form.on('Sales Invoice', {
if (frm.doc.company)
{
frappe.call({
- method:"frappe.contacts.doctype.address.address.get_default_address",
- args:{ doctype:'Company',name:frm.doc.company},
+ method:"erpnext.setup.doctype.company.company.get_default_company_address",
+ args:{name:frm.doc.company, existing_address: frm.doc.company_address},
callback: function(r){
if (r.message){
frm.set_value("company_address",r.message)
@@ -789,22 +778,21 @@ frappe.ui.form.on('Sales Invoice', {
method: "frappe.client.get_value",
args:{
doctype: "Patient",
- filters: {"name": frm.doc.patient},
+ filters: {
+ "name": frm.doc.patient
+ },
fieldname: "customer"
},
- callback:function(patient_customer) {
- if(patient_customer){
- frm.set_value("customer", patient_customer.message.customer);
- frm.refresh_fields();
+ callback:function(r) {
+ if(r && r.message.customer){
+ frm.set_value("customer", r.message.customer);
}
}
});
}
- else{
- frm.set_value("customer", '');
- }
}
},
+
refresh: function(frm) {
if (frappe.boot.active_domains.includes("Healthcare")){
frm.set_df_property("patient", "hidden", 0);
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 96aceac8cd9..33ee7a2974e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-24 19:29:05",
@@ -774,7 +775,7 @@
},
{
"fieldname": "other_charges_calculation",
- "fieldtype": "Text",
+ "fieldtype": "Long Text",
"label": "Taxes and Charges Calculation",
"no_copy": 1,
"oldfieldtype": "HTML",
@@ -1567,7 +1568,8 @@
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
- "modified": "2019-10-05 21:39:49.235990",
+ "links": [],
+ "modified": "2019-12-30 19:15:59.580414",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index e1256a78d95..703df796c06 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -90,6 +90,7 @@ class SalesInvoice(SellingController):
self.validate_account_for_change_amount()
self.validate_fixed_asset()
self.set_income_account_for_fixed_assets()
+ self.validate_item_cost_centers()
validate_inter_company_party(self.doctype, self.customer, self.company, self.inter_company_invoice_reference)
if cint(self.is_pos):
@@ -136,6 +137,22 @@ class SalesInvoice(SellingController):
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
validate_loyalty_points(self, self.loyalty_points)
+ def validate_fixed_asset(self):
+ for d in self.get("items"):
+ if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
+ asset = frappe.get_doc("Asset", d.asset)
+ if self.doctype == "Sales Invoice" and self.docstatus == 1:
+ if self.update_stock:
+ frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
+
+ elif asset.status in ("Scrapped", "Cancelled", "Sold"):
+ frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status))
+
+ def validate_item_cost_centers(self):
+ for item in self.items:
+ cost_center_company = frappe.get_cached_value("Cost Center", item.cost_center, "company")
+ if cost_center_company != self.company:
+ frappe.throw(_("Row #{0}: Cost Center {1} does not belong to company {2}").format(frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company)))
def before_save(self):
set_account_for_mode_of_payment(self)
@@ -338,7 +355,8 @@ class SalesInvoice(SellingController):
"print_format": print_format,
"allow_edit_rate": pos.get("allow_user_to_edit_rate"),
"allow_edit_discount": pos.get("allow_user_to_edit_discount"),
- "campaign": pos.get("campaign")
+ "campaign": pos.get("campaign"),
+ "allow_print_before_pay": pos.get("allow_print_before_pay")
}
def update_time_sheet(self, sales_invoice):
@@ -525,9 +543,7 @@ class SalesInvoice(SellingController):
for i in dic:
if frappe.db.get_single_value('Selling Settings', dic[i][0]) == 'Yes':
for d in self.get('items'):
- is_stock_item = frappe.get_cached_value('Item', d.item_code, 'is_stock_item')
- if (d.item_code and is_stock_item == 1\
- and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])):
+ if (d.item_code and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])):
msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1)
@@ -686,7 +702,6 @@ class SalesInvoice(SellingController):
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False):
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
-
if not gl_entries:
gl_entries = self.get_gl_entries()
@@ -944,7 +959,7 @@ class SalesInvoice(SellingController):
)
def make_gle_for_rounding_adjustment(self, gl_entries):
- if flt(self.rounding_adjustment, self.precision("rounding_adjustment")):
+ if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment:
round_off_account, round_off_cost_center = \
get_round_off_account_and_cost_center(self.company)
@@ -992,10 +1007,8 @@ class SalesInvoice(SellingController):
continue
for serial_no in item.serial_no.split("\n"):
- if serial_no and frappe.db.exists('Serial No', serial_no):
- sno = frappe.get_doc('Serial No', serial_no)
- sno.sales_invoice = invoice
- sno.db_update()
+ if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code:
+ frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice)
def validate_serial_numbers(self):
"""
@@ -1041,12 +1054,18 @@ class SalesInvoice(SellingController):
continue
for serial_no in item.serial_no.split("\n"):
- sales_invoice = frappe.db.get_value("Serial No", serial_no, "sales_invoice")
- if sales_invoice and self.name != sales_invoice:
- sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company")
+ serial_no_details = frappe.db.get_value("Serial No", serial_no,
+ ["sales_invoice", "item_code"], as_dict=1)
+
+ if not serial_no_details:
+ continue
+
+ if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \
+ and self.name != serial_no_details.sales_invoice:
+ sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company")
if sales_invoice_company == self.company:
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}"
- .format(serial_no, sales_invoice)))
+ .format(serial_no, serial_no_details.sales_invoice)))
def update_project(self):
if self.project:
@@ -1231,7 +1250,8 @@ class SalesInvoice(SellingController):
self.status = "Unpaid and Discounted"
elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) >= getdate(nowdate()):
self.status = "Unpaid"
- elif flt(self.outstanding_amount) < 0 and self.is_return==0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
+ #Check if outstanding amount is 0 due to credit note issued against invoice
+ elif flt(self.outstanding_amount) <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
self.status = "Credit Note Issued"
elif self.is_return == 1:
self.status = "Return"
diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json
index 9c8de7d5a24..ebe6e3da8df 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_records.json
+++ b/erpnext/accounts/doctype/sales_invoice/test_records.json
@@ -68,8 +68,6 @@
"selling_price_list": "_Test Price List",
"territory": "_Test Territory"
},
-
-
{
"company": "_Test Company",
"conversion_rate": 1.0,
@@ -276,7 +274,6 @@
"uom": "_Test UOM 1",
"conversion_factor": 1,
"stock_uom": "_Test UOM 1"
-
},
{
"cost_center": "_Test Cost Center - _TC",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 4f253b69f79..a2a47b3a19c 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
import unittest, copy, time
-from frappe.utils import nowdate, flt, getdate, cint
+from frappe.utils import nowdate, flt, getdate, cint, add_days
from frappe.model.dynamic_links import get_dynamic_link_map
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
@@ -20,6 +20,9 @@ from erpnext.stock.doctype.item.test_item import create_item
from six import iteritems
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
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
class TestSalesInvoice(unittest.TestCase):
def make(self):
@@ -550,7 +553,6 @@ class TestSalesInvoice(unittest.TestCase):
si.get("taxes")[6].tax_amount = 2
si.insert()
- print(si.name)
expected_values = [
{
@@ -679,56 +681,67 @@ class TestSalesInvoice(unittest.TestCase):
self.assertFalse(gle)
def test_pos_gl_entry_with_perpetual_inventory(self):
- set_perpetual_inventory()
make_pos_profile()
- self._insert_purchase_receipt()
- pos = copy.deepcopy(test_records[1])
- pos["is_pos"] = 1
- pos["update_stock"] = 1
- pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300},
- {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300}]
+ pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
+
+ pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
+
+ pos.is_pos = 1
+ pos.update_stock = 1
+
+ pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
+ pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50})
+
+ taxes = get_taxes_and_charges()
+ pos.taxes = []
+ for tax in taxes:
+ pos.append("taxes", tax)
si = frappe.copy_doc(pos)
si.insert()
si.submit()
+ self.assertEqual(si.paid_amount, 100.0)
- self.assertEqual(si.paid_amount, 600.0)
-
- self.pos_gl_entry(si, pos, 300)
+ self.pos_gl_entry(si, pos, 50)
def test_pos_change_amount(self):
- set_perpetual_inventory()
make_pos_profile()
- self._insert_purchase_receipt()
- pos = copy.deepcopy(test_records[1])
- pos["is_pos"] = 1
- pos["update_stock"] = 1
- pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300},
- {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 340}]
+ pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
- si = frappe.copy_doc(pos)
- si.change_amount = 5.0
- si.insert()
- si.submit()
+ pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
- self.assertEqual(si.grand_total, 630.0)
- self.assertEqual(si.write_off_amount, -5)
+ pos.is_pos = 1
+ pos.update_stock = 1
+
+ pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
+ pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60})
+
+ pos.change_amount = 5.0
+ pos.insert()
+ pos.submit()
+
+ self.assertEqual(pos.grand_total, 100.0)
+ self.assertEqual(pos.write_off_amount, -5)
def test_make_pos_invoice(self):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
- set_perpetual_inventory()
-
make_pos_profile()
- self._insert_purchase_receipt()
+ pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
+ pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
- pos = copy.deepcopy(test_records[1])
- pos["is_pos"] = 1
- pos["update_stock"] = 1
- pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300},
- {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 330}]
+ pos.is_pos = 1
+ pos.update_stock = 1
+
+ pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
+ pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 50})
+
+ taxes = get_taxes_and_charges()
+ pos.taxes = []
+ for tax in taxes:
+ pos.append("taxes", tax)
invoice_data = [{'09052016142': pos}]
si = make_invoice(invoice_data).get('invoice')
@@ -736,16 +749,15 @@ class TestSalesInvoice(unittest.TestCase):
sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': '09052016142', 'docstatus': 1})
si = frappe.get_doc('Sales Invoice', sales_invoice[0].name)
- self.assertEqual(si.grand_total, 630.0)
- self.pos_gl_entry(si, pos, 330)
+ self.assertEqual(si.grand_total, 100)
+
+ self.pos_gl_entry(si, pos, 50)
def test_make_pos_invoice_in_draft(self):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
from erpnext.stock.doctype.item.test_item import make_item
- set_perpetual_inventory()
-
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
if allow_negative_stock:
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
@@ -789,7 +801,7 @@ class TestSalesInvoice(unittest.TestCase):
si.name, as_dict=1)[0]
self.assertTrue(sle)
self.assertEqual([sle.item_code, sle.warehouse, sle.actual_qty],
- ["_Test Item", "_Test Warehouse - _TC", -1.0])
+ ['_Test FG Item', 'Stores - TCP1', -1.0])
# check gl entries
gl_entries = frappe.db.sql("""select account, debit, credit
@@ -797,19 +809,19 @@ class TestSalesInvoice(unittest.TestCase):
order by account asc, debit asc, credit asc""", si.name, as_dict=1)
self.assertTrue(gl_entries)
- stock_in_hand = get_inventory_account('_Test Company')
-
+ stock_in_hand = get_inventory_account('_Test Company with perpetual inventory')
expected_gl_entries = sorted([
- [si.debit_to, 630.0, 0.0],
- [pos["items"][0]["income_account"], 0.0, 500.0],
- [pos["taxes"][0]["account_head"], 0.0, 80.0],
- [pos["taxes"][1]["account_head"], 0.0, 50.0],
+ [si.debit_to, 100.0, 0.0],
+ [pos.items[0].income_account, 0.0, 89.09],
+ ['Round Off - TCP1', 0.0, 0.01],
+ [pos.taxes[0].account_head, 0.0, 10.69],
+ [pos.taxes[1].account_head, 0.0, 0.21],
[stock_in_hand, 0.0, abs(sle.stock_value_difference)],
- [pos["items"][0]["expense_account"], abs(sle.stock_value_difference), 0.0],
- [si.debit_to, 0.0, 300.0],
+ [pos.items[0].expense_account, abs(sle.stock_value_difference), 0.0],
+ [si.debit_to, 0.0, 50.0],
[si.debit_to, 0.0, cash_amount],
- ["_Test Bank - _TC", 300.0, 0.0],
- ["Cash - _TC", cash_amount, 0.0]
+ ["_Test Bank - TCP1", 50, 0.0],
+ ["Cash - TCP1", cash_amount, 0.0]
])
for i, gle in enumerate(sorted(gl_entries, key=lambda gle: gle.account)):
@@ -823,9 +835,9 @@ class TestSalesInvoice(unittest.TestCase):
self.assertFalse(gle)
- set_perpetual_inventory(0)
frappe.db.sql("delete from `tabPOS Profile`")
+ si.delete()
def test_pos_si_without_payment(self):
set_perpetual_inventory()
@@ -1008,7 +1020,6 @@ class TestSalesInvoice(unittest.TestCase):
"""
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
- from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
se = make_serialized_item()
@@ -1023,14 +1034,17 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.get("items")[0].serial_no, dn.get("items")[0].serial_no)
def test_return_sales_invoice(self):
- set_perpetual_inventory()
- make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
+ make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100)
- actual_qty_0 = get_qty_after_transaction()
+ actual_qty_0 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
- si = create_sales_invoice(qty=5, rate=500, update_stock=1)
+ si = create_sales_invoice(qty = 5, rate=500, update_stock=1, company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1")
+
+
+ actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
+
+ frappe.db.commit()
- actual_qty_1 = get_qty_after_transaction()
self.assertEqual(actual_qty_0 - 5, actual_qty_1)
# outgoing_rate
@@ -1038,10 +1052,9 @@ class TestSalesInvoice(unittest.TestCase):
"voucher_no": si.name}, "stock_value_difference") / 5
# return entry
- si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1)
-
- actual_qty_2 = get_qty_after_transaction()
+ si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1, company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1")
+ actual_qty_2 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
self.assertEqual(actual_qty_1 + 2, actual_qty_2)
incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
@@ -1049,7 +1062,7 @@ class TestSalesInvoice(unittest.TestCase):
["incoming_rate", "stock_value_difference"])
self.assertEqual(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3)))
- stock_in_hand_account = get_inventory_account('_Test Company', si1.items[0].warehouse)
+ stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory', si1.items[0].warehouse)
# Check gl entry
gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
@@ -1058,7 +1071,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(gle_warehouse_amount, stock_value_difference)
party_credited = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
- "voucher_no": si1.name, "account": "Debtors - _TC", "party": "_Test Customer"}, "credit")
+ "voucher_no": si1.name, "account": "Debtors - TCP1", "party": "_Test Customer"}, "credit")
self.assertEqual(party_credited, 1000)
@@ -1066,7 +1079,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertFalse(si1.outstanding_amount)
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
- set_perpetual_inventory(0)
def test_discount_on_net_total(self):
si = frappe.copy_doc(test_records[2])
@@ -1524,6 +1536,8 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.total_taxes_and_charges, 577.05)
self.assertEqual(si.grand_total, 1827.05)
+
+
def test_create_invoice_without_terms(self):
si = create_sales_invoice(do_not_save=1)
self.assertFalse(si.get('payment_schedule'))
@@ -1833,6 +1847,26 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
+ def test_item_tax_validity(self):
+ item = frappe.get_doc("Item", "_Test Item 2")
+
+ if item.taxes:
+ item.taxes = []
+ item.save()
+
+ item.append("taxes", {
+ "item_tax_template": "_Test Item Tax Template 1",
+ "valid_from": add_days(nowdate(), 1)
+ })
+
+ item.save()
+
+ sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1)
+ sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1"
+ self.assertRaises(frappe.ValidationError, sales_invoice.save)
+
+ item.taxes = []
+ item.save()
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
@@ -1930,4 +1964,29 @@ def get_outstanding_amount(against_voucher_type, against_voucher, account, party
if against_voucher_type == 'Purchase Invoice':
bal = bal * -1
- return bal
\ No newline at end of file
+ return bal
+
+def get_taxes_and_charges():
+ return [{
+ "account_head": "_Test Account Excise Duty - TCP1",
+ "charge_type": "On Net Total",
+ "cost_center": "Main - TCP1",
+ "description": "Excise Duty",
+ "doctype": "Sales Taxes and Charges",
+ "idx": 1,
+ "included_in_print_rate": 1,
+ "parentfield": "taxes",
+ "rate": 12
+ },
+ {
+ "account_head": "_Test Account Education Cess - TCP1",
+ "charge_type": "On Previous Row Amount",
+ "cost_center": "Main - TCP1",
+ "description": "Education Cess",
+ "doctype": "Sales Taxes and Charges",
+ "idx": 2,
+ "included_in_print_rate": 1,
+ "parentfield": "taxes",
+ "rate": 2,
+ "row_id": 1
+ }]
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index 779ac4f656c..b2294e4318f 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2013-06-04 11:02:19",
"doctype": "DocType",
@@ -484,7 +485,8 @@
"depends_on": "enable_deferred_revenue",
"fieldname": "service_stop_date",
"fieldtype": "Date",
- "label": "Service Stop Date"
+ "label": "Service Stop Date",
+ "no_copy": 1
},
{
"default": "0",
@@ -500,13 +502,15 @@
"depends_on": "enable_deferred_revenue",
"fieldname": "service_start_date",
"fieldtype": "Date",
- "label": "Service Start Date"
+ "label": "Service Start Date",
+ "no_copy": 1
},
{
"depends_on": "enable_deferred_revenue",
"fieldname": "service_end_date",
"fieldtype": "Date",
- "label": "Service End Date"
+ "label": "Service End Date",
+ "no_copy": 1
},
{
"collapsible": 1,
@@ -783,7 +787,8 @@
],
"idx": 1,
"istable": 1,
- "modified": "2019-07-16 16:36:46.527606",
+ "links": [],
+ "modified": "2019-12-04 12:22:38.517710",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
index 29e15d165fa..19781bdffaa 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
@@ -1,299 +1,119 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:title",
- "beta": 0,
- "creation": "2013-01-10 16:34:09",
- "custom": 0,
- "description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "creation": "2013-01-10 16:34:09",
+ "description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "is_default",
+ "disabled",
+ "column_break_3",
+ "company",
+ "tax_category",
+ "section_break_5",
+ "taxes"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "title",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "label": "Title",
+ "no_copy": 1,
+ "oldfieldname": "title",
+ "oldfieldtype": "Data",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "is_default",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Default",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "is_default",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Default"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "disabled",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Disabled",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "label": "Disabled"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "company",
- "oldfieldtype": "Link",
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Company",
+ "oldfieldname": "company",
+ "oldfieldtype": "Link",
+ "options": "Company",
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_5",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "* Will be calculated in the transaction.",
- "fieldname": "taxes",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sales Taxes and Charges",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "other_charges",
- "oldfieldtype": "Table",
- "options": "Sales Taxes and Charges",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "description": "* Will be calculated in the transaction.",
+ "fieldname": "taxes",
+ "fieldtype": "Table",
+ "label": "Sales Taxes and Charges",
+ "oldfieldname": "other_charges",
+ "oldfieldtype": "Table",
+ "options": "Sales Taxes and Charges"
+ },
+ {
+ "fieldname": "tax_category",
+ "fieldtype": "Link",
+ "label": "Tax Category",
+ "options": "Tax Category"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-money",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-11-07 05:18:41.743257",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Sales Taxes and Charges Template",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-money",
+ "idx": 1,
+ "modified": "2019-11-25 13:06:03.279099",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Sales Taxes and Charges Template",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 1,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales User",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales User"
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Master Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Master Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_order": "ASC",
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.js b/erpnext/accounts/doctype/share_transfer/share_transfer.js
index 364ca6fd282..1cad4dfae3d 100644
--- a/erpnext/accounts/doctype/share_transfer/share_transfer.js
+++ b/erpnext/accounts/doctype/share_transfer/share_transfer.js
@@ -21,6 +21,8 @@ frappe.ui.form.on('Share Transfer', {
erpnext.share_transfer.make_jv(frm);
});
}
+
+ frm.toggle_reqd("asset_account", frm.doc.transfer_type != "Transfer");
},
no_of_shares: (frm) => {
if (frm.doc.rate != undefined || frm.doc.rate != null){
@@ -56,6 +58,10 @@ frappe.ui.form.on('Share Transfer', {
};
});
}
+ },
+
+ transfer_type: function(frm) {
+ frm.toggle_reqd("asset_account", frm.doc.transfer_type != "Transfer");
}
});
diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.json b/erpnext/accounts/doctype/share_transfer/share_transfer.json
index 24c4569b003..59a305317d8 100644
--- a/erpnext/accounts/doctype/share_transfer/share_transfer.json
+++ b/erpnext/accounts/doctype/share_transfer/share_transfer.json
@@ -1,881 +1,242 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "ACC-SHT-.YYYY.-.#####",
- "beta": 0,
- "creation": "2017-12-25 17:18:03.143726",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "autoname": "ACC-SHT-.YYYY.-.#####",
+ "creation": "2017-12-25 17:18:03.143726",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "transfer_type",
+ "column_break_1",
+ "date",
+ "section_break_1",
+ "from_shareholder",
+ "from_folio_no",
+ "column_break_3",
+ "to_shareholder",
+ "to_folio_no",
+ "section_break_10",
+ "equity_or_liability_account",
+ "column_break_12",
+ "asset_account",
+ "section_break_4",
+ "share_type",
+ "from_no",
+ "rate",
+ "column_break_8",
+ "no_of_shares",
+ "to_no",
+ "amount",
+ "section_break_11",
+ "company",
+ "section_break_6",
+ "remarks",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "transfer_type",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Transfer Type",
- "length": 0,
- "no_copy": 0,
- "options": "\nIssue\nPurchase\nTransfer",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "transfer_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Transfer Type",
+ "options": "\nIssue\nPurchase\nTransfer",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_1",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_1",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "label": "Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_1",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_1",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.transfer_type != 'Issue'",
- "fieldname": "from_shareholder",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "From Shareholder",
- "length": 0,
- "no_copy": 0,
- "options": "Shareholder",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:doc.transfer_type != 'Issue'",
+ "fieldname": "from_shareholder",
+ "fieldtype": "Link",
+ "label": "From Shareholder",
+ "options": "Shareholder"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.transfer_type != 'Issue'",
- "fetch_from": "from_shareholder.folio_no",
- "fieldname": "from_folio_no",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "From Folio No",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:doc.transfer_type != 'Issue'",
+ "fetch_from": "from_shareholder.folio_no",
+ "fieldname": "from_folio_no",
+ "fieldtype": "Data",
+ "label": "From Folio No"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.company",
- "fieldname": "equity_or_liability_account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Equity/Liability Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:doc.company",
+ "fieldname": "equity_or_liability_account",
+ "fieldtype": "Link",
+ "label": "Equity/Liability Account",
+ "options": "Account",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:(doc.transfer_type != 'Transfer') && (doc.company)",
- "fieldname": "asset_account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Asset Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:(doc.transfer_type != 'Transfer') && (doc.company)",
+ "fieldname": "asset_account",
+ "fieldtype": "Link",
+ "label": "Asset Account",
+ "options": "Account"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.transfer_type != 'Purchase'",
- "fieldname": "to_shareholder",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "To Shareholder",
- "length": 0,
- "no_copy": 0,
- "options": "Shareholder",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:doc.transfer_type != 'Purchase'",
+ "fieldname": "to_shareholder",
+ "fieldtype": "Link",
+ "label": "To Shareholder",
+ "options": "Shareholder"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.transfer_type != 'Purchase'",
- "fetch_from": "to_shareholder.folio_no",
- "fieldname": "to_folio_no",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "To Folio No",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:doc.transfer_type != 'Purchase'",
+ "fetch_from": "to_shareholder.folio_no",
+ "fieldname": "to_folio_no",
+ "fieldtype": "Data",
+ "label": "To Folio No"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_4",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "share_type",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Share Type",
- "length": 0,
- "no_copy": 0,
- "options": "Share Type",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "share_type",
+ "fieldtype": "Link",
+ "label": "Share Type",
+ "options": "Share Type",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "(including)",
- "fieldname": "from_no",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "From No",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "description": "(including)",
+ "fieldname": "from_no",
+ "fieldtype": "Int",
+ "label": "From No",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "rate",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "label": "Rate",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_8",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "no_of_shares",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "No of Shares",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "no_of_shares",
+ "fieldtype": "Int",
+ "label": "No of Shares",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "(including)",
- "fieldname": "to_no",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "To No",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "description": "(including)",
+ "fieldname": "to_no",
+ "fieldtype": "Int",
+ "label": "To No",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "label": "Amount",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_11",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_11",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_6",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "remarks",
- "fieldtype": "Long Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Remarks",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "remarks",
+ "fieldtype": "Long Text",
+ "label": "Remarks"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Share Transfer",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Share Transfer",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_10",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-09-18 14:14:46.233568",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Share Transfer",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "modified": "2019-12-20 14:48:01.990600",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Share Transfer",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.py b/erpnext/accounts/doctype/share_transfer/share_transfer.py
index e95c69413f5..65f248e7bde 100644
--- a/erpnext/accounts/doctype/share_transfer/share_transfer.py
+++ b/erpnext/accounts/doctype/share_transfer/share_transfer.py
@@ -13,9 +13,9 @@ from frappe.utils import nowdate
class ShareDontExists(ValidationError): pass
class ShareTransfer(Document):
- def before_submit(self):
+ def on_submit(self):
if self.transfer_type == 'Issue':
- shareholder = self.get_shareholder_doc(self.company)
+ shareholder = self.get_company_shareholder()
shareholder.append('share_balance', {
'share_type': self.share_type,
'from_no': self.from_no,
@@ -28,7 +28,7 @@ class ShareTransfer(Document):
})
shareholder.save()
- doc = frappe.get_doc('Shareholder', self.to_shareholder)
+ doc = self.get_shareholder_doc(self.to_shareholder)
doc.append('share_balance', {
'share_type': self.share_type,
'from_no': self.from_no,
@@ -41,11 +41,11 @@ class ShareTransfer(Document):
elif self.transfer_type == 'Purchase':
self.remove_shares(self.from_shareholder)
- self.remove_shares(self.get_shareholder_doc(self.company).name)
+ self.remove_shares(self.get_company_shareholder().name)
elif self.transfer_type == 'Transfer':
self.remove_shares(self.from_shareholder)
- doc = frappe.get_doc('Shareholder', self.to_shareholder)
+ doc = self.get_shareholder_doc(self.to_shareholder)
doc.append('share_balance', {
'share_type': self.share_type,
'from_no': self.from_no,
@@ -56,143 +56,127 @@ class ShareTransfer(Document):
})
doc.save()
+ def on_cancel(self):
+ if self.transfer_type == 'Issue':
+ compnay_shareholder = self.get_company_shareholder()
+ self.remove_shares(compnay_shareholder.name)
+ self.remove_shares(self.to_shareholder)
+
+ elif self.transfer_type == 'Purchase':
+ compnay_shareholder = self.get_company_shareholder()
+ from_shareholder = self.get_shareholder_doc(self.from_shareholder)
+
+ from_shareholder.append('share_balance', {
+ 'share_type': self.share_type,
+ 'from_no': self.from_no,
+ 'to_no': self.to_no,
+ 'rate': self.rate,
+ 'amount': self.amount,
+ 'no_of_shares': self.no_of_shares
+ })
+
+ from_shareholder.save()
+
+ compnay_shareholder.append('share_balance', {
+ 'share_type': self.share_type,
+ 'from_no': self.from_no,
+ 'to_no': self.to_no,
+ 'rate': self.rate,
+ 'amount': self.amount,
+ 'no_of_shares': self.no_of_shares
+ })
+
+ compnay_shareholder.save()
+
+ elif self.transfer_type == 'Transfer':
+ self.remove_shares(self.to_shareholder)
+ from_shareholder = self.get_shareholder_doc(self.from_shareholder)
+ from_shareholder.append('share_balance', {
+ 'share_type': self.share_type,
+ 'from_no': self.from_no,
+ 'to_no': self.to_no,
+ 'rate': self.rate,
+ 'amount': self.amount,
+ 'no_of_shares': self.no_of_shares
+ })
+ from_shareholder.save()
+
def validate(self):
+ self.get_company_shareholder()
self.basic_validations()
self.folio_no_validation()
+
if self.transfer_type == 'Issue':
- if not self.get_shareholder_doc(self.company):
- shareholder = frappe.get_doc({
- 'doctype': 'Shareholder',
- 'title': self.company,
- 'company': self.company,
- 'is_company': 1
- })
- shareholder.insert()
- # validate share doesnt exist in company
- ret_val = self.share_exists(self.get_shareholder_doc(self.company).name)
- if ret_val != False:
+ # validate share doesn't exist in company
+ ret_val = self.share_exists(self.get_company_shareholder().name)
+ if ret_val in ('Complete', 'Partial'):
frappe.throw(_('The shares already exist'), frappe.DuplicateEntryError)
else:
# validate share exists with from_shareholder
ret_val = self.share_exists(self.from_shareholder)
- if ret_val != True:
+ if ret_val in ('Outside', 'Partial'):
frappe.throw(_("The shares don't exist with the {0}")
.format(self.from_shareholder), ShareDontExists)
def basic_validations(self):
if self.transfer_type == 'Purchase':
self.to_shareholder = ''
- if self.from_shareholder is None or self.from_shareholder is '':
+ if not self.from_shareholder:
frappe.throw(_('The field From Shareholder cannot be blank'))
- if self.from_folio_no is None or self.from_folio_no is '':
+ if not self.from_folio_no:
self.to_folio_no = self.autoname_folio(self.to_shareholder)
- if self.asset_account is None:
+ if not self.asset_account:
frappe.throw(_('The field Asset Account cannot be blank'))
elif (self.transfer_type == 'Issue'):
self.from_shareholder = ''
- if self.to_shareholder is None or self.to_shareholder == '':
+ if not self.to_shareholder:
frappe.throw(_('The field To Shareholder cannot be blank'))
- if self.to_folio_no is None or self.to_folio_no is '':
+ if not self.to_folio_no:
self.to_folio_no = self.autoname_folio(self.to_shareholder)
- if self.asset_account is None:
+ if not self.asset_account:
frappe.throw(_('The field Asset Account cannot be blank'))
else:
- if self.from_shareholder is None or self.to_shareholder is None:
+ if not self.from_shareholder or not self.to_shareholder:
frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank'))
- if self.to_folio_no is None or self.to_folio_no is '':
+ if not self.to_folio_no:
self.to_folio_no = self.autoname_folio(self.to_shareholder)
- if self.equity_or_liability_account is None:
+ if not self.equity_or_liability_account:
frappe.throw(_('The field Equity/Liability Account cannot be blank'))
if self.from_shareholder == self.to_shareholder:
frappe.throw(_('The seller and the buyer cannot be the same'))
if self.no_of_shares != self.to_no - self.from_no + 1:
frappe.throw(_('The number of shares and the share numbers are inconsistent'))
- if self.amount is None:
+ if not self.amount:
self.amount = self.rate * self.no_of_shares
if self.amount != self.rate * self.no_of_shares:
frappe.throw(_('There are inconsistencies between the rate, no of shares and the amount calculated'))
def share_exists(self, shareholder):
- # return True if exits,
- # False if completely doesn't exist,
- # 'partially exists' if partailly doesn't exist
- ret_val = self.recursive_share_check(shareholder, self.share_type,
- query = {
- 'from_no': self.from_no,
- 'to_no': self.to_no
- }
- )
- if all(boolean == True for boolean in ret_val):
- return True
- elif True in ret_val:
- return 'partially exists'
- else:
- return False
-
- def recursive_share_check(self, shareholder, share_type, query):
- # query = {'from_no': share_starting_no, 'to_no': share_ending_no}
- # Recursive check if a given part of shares is held by the shareholder
- # return a list containing True and False
- # Eg. [True, False, True]
- # All True implies its completely inside
- # All False implies its completely outside
- # A mix implies its partially inside/outside
- does_share_exist = []
- doc = frappe.get_doc('Shareholder', shareholder)
+ doc = self.get_shareholder_doc(shareholder)
for entry in doc.share_balance:
- if entry.share_type != share_type or \
- entry.from_no > query['to_no'] or \
- entry.to_no < query['from_no']:
+ if entry.share_type != self.share_type or \
+ entry.from_no > self.to_no or \
+ entry.to_no < self.from_no:
continue # since query lies outside bounds
- elif entry.from_no <= query['from_no'] and entry.to_no >= query['to_no']:
- return [True] # absolute truth!
- elif entry.from_no >= query['from_no'] and entry.to_no <= query['to_no']:
- # split and check
- does_share_exist.extend(self.recursive_share_check(shareholder,
- share_type,
- {
- 'from_no': query['from_no'],
- 'to_no': entry.from_no - 1
- }
- ))
- does_share_exist.append(True)
- does_share_exist.extend(self.recursive_share_check(shareholder,
- share_type,
- {
- 'from_no': entry.to_no + 1,
- 'to_no': query['to_no']
- }
- ))
- elif query['from_no'] <= entry.from_no <= query['to_no'] and entry.to_no >= query['to_no']:
- does_share_exist.extend(self.recursive_share_check(shareholder,
- share_type,
- {
- 'from_no': query['from_no'],
- 'to_no': entry.from_no - 1
- }
- ))
- elif query['from_no'] <= entry.to_no <= query['to_no'] and entry.from_no <= query['from_no']:
- does_share_exist.extend(self.recursive_share_check(shareholder,
- share_type,
- {
- 'from_no': entry.to_no + 1,
- 'to_no': query['to_no']
- }
- ))
+ elif entry.from_no <= self.from_no and entry.to_no >= self.to_no: #both inside
+ return 'Complete' # absolute truth!
+ elif entry.from_no <= self.from_no <= self.to_no:
+ return 'Partial'
+ elif entry.from_no <= self.to_no <= entry.to_no:
+ return 'Partial'
- does_share_exist.append(False)
- return does_share_exist
+ return 'Outside'
def folio_no_validation(self):
shareholders = ['from_shareholder', 'to_shareholder']
shareholders = [shareholder for shareholder in shareholders if self.get(shareholder) is not '']
for shareholder in shareholders:
- doc = frappe.get_doc('Shareholder', self.get(shareholder))
+ doc = self.get_shareholder_doc(self.get(shareholder))
if doc.company != self.company:
frappe.throw(_('The shareholder does not belong to this company'))
- if doc.folio_no is '' or doc.folio_no is None:
+ if not doc.folio_no:
doc.folio_no = self.from_folio_no \
- if (shareholder == 'from_shareholder') else self.to_folio_no;
+ if (shareholder == 'from_shareholder') else self.to_folio_no
doc.save()
else:
if doc.folio_no and doc.folio_no != (self.from_folio_no if (shareholder == 'from_shareholder') else self.to_folio_no):
@@ -200,24 +184,14 @@ class ShareTransfer(Document):
def autoname_folio(self, shareholder, is_company=False):
if is_company:
- doc = self.get_shareholder_doc(shareholder)
+ doc = self.get_company_shareholder()
else:
- doc = frappe.get_doc('Shareholder' , shareholder)
+ doc = self.get_shareholder_doc(shareholder)
doc.folio_no = make_autoname('FN.#####')
doc.save()
return doc.folio_no
def remove_shares(self, shareholder):
- self.iterative_share_removal(shareholder, self.share_type,
- {
- 'from_no': self.from_no,
- 'to_no' : self.to_no
- },
- rate = self.rate,
- amount = self.amount
- )
-
- def iterative_share_removal(self, shareholder, share_type, query, rate, amount):
# query = {'from_no': share_starting_no, 'to_no': share_ending_no}
# Shares exist for sure
# Iterate over all entries and modify entry if in entry
@@ -227,31 +201,31 @@ class ShareTransfer(Document):
for entry in current_entries:
# use spaceage logic here
- if entry.share_type != share_type or \
- entry.from_no > query['to_no'] or \
- entry.to_no < query['from_no']:
+ if entry.share_type != self.share_type or \
+ entry.from_no > self.to_no or \
+ entry.to_no < self.from_no:
new_entries.append(entry)
continue # since query lies outside bounds
- elif entry.from_no <= query['from_no'] and entry.to_no >= query['to_no']:
+ elif entry.from_no <= self.from_no and entry.to_no >= self.to_no:
#split
- if entry.from_no == query['from_no']:
- if entry.to_no == query['to_no']:
+ if entry.from_no == self.from_no:
+ if entry.to_no == self.to_no:
pass #nothing to append
else:
- new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
+ new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
else:
- if entry.to_no == query['to_no']:
- new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
+ if entry.to_no == self.to_no:
+ new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
else:
- new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
- new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
- elif entry.from_no >= query['from_no'] and entry.to_no <= query['to_no']:
+ new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
+ new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
+ elif entry.from_no >= self.from_no and entry.to_no <= self.to_no:
# split and check
pass #nothing to append
- elif query['from_no'] <= entry.from_no <= query['to_no'] and entry.to_no >= query['to_no']:
- new_entries.append(self.return_share_balance_entry(query['to_no']+1, entry.to_no, entry.rate))
- elif query['from_no'] <= entry.to_no <= query['to_no'] and entry.from_no <= query['from_no']:
- new_entries.append(self.return_share_balance_entry(entry.from_no, query['from_no']-1, entry.rate))
+ elif self.from_no <= entry.from_no <= self.to_no and entry.to_no >= self.to_no:
+ new_entries.append(self.return_share_balance_entry(self.to_no+1, entry.to_no, entry.rate))
+ elif self.from_no <= entry.to_no <= self.to_no and entry.from_no <= self.from_no:
+ new_entries.append(self.return_share_balance_entry(entry.from_no, self.from_no-1, entry.rate))
else:
new_entries.append(entry)
@@ -272,16 +246,34 @@ class ShareTransfer(Document):
}
def get_shareholder_doc(self, shareholder):
- # Get Shareholder doc based on the Shareholder title
- doc = frappe.get_list('Shareholder',
- filters = [
- ('Shareholder', 'title', '=', shareholder)
- ]
- )
- if len(doc) == 1:
- return frappe.get_doc('Shareholder', doc[0]['name'])
- else: #It will necessarily by 0 indicating it doesn't exist
- return False
+ # Get Shareholder doc based on the Shareholder name
+ if shareholder:
+ query_filters = {'name': shareholder}
+
+ name = frappe.db.get_value('Shareholder', {'name': shareholder}, 'name')
+
+ return frappe.get_doc('Shareholder', name)
+
+ def get_company_shareholder(self):
+ # Get company doc or create one if not present
+ company_shareholder = frappe.db.get_value('Shareholder',
+ {
+ 'company': self.company,
+ 'is_company': 1
+ }, 'name')
+
+ if company_shareholder:
+ return frappe.get_doc('Shareholder', company_shareholder)
+ else:
+ shareholder = frappe.get_doc({
+ 'doctype': 'Shareholder',
+ 'title': self.company,
+ 'company': self.company,
+ 'is_company': 1
+ })
+ shareholder.insert()
+
+ return shareholder
@frappe.whitelist()
def make_jv_entry( company, account, amount, payment_account,\
diff --git a/erpnext/accounts/doctype/share_transfer/test_share_transfer.py b/erpnext/accounts/doctype/share_transfer/test_share_transfer.py
index 910dfd05dab..2ff9b02129f 100644
--- a/erpnext/accounts/doctype/share_transfer/test_share_transfer.py
+++ b/erpnext/accounts/doctype/share_transfer/test_share_transfer.py
@@ -15,73 +15,73 @@ class TestShareTransfer(unittest.TestCase):
frappe.db.sql("delete from `tabShare Balance`")
share_transfers = [
{
- "doctype" : "Share Transfer",
- "transfer_type" : "Issue",
- "date" : "2018-01-01",
- "to_shareholder" : "SH-00001",
- "share_type" : "Equity",
- "from_no" : 1,
- "to_no" : 500,
- "no_of_shares" : 500,
- "rate" : 10,
- "company" : "_Test Company",
- "asset_account" : "Cash - _TC",
+ "doctype": "Share Transfer",
+ "transfer_type": "Issue",
+ "date": "2018-01-01",
+ "to_shareholder": "SH-00001",
+ "share_type": "Equity",
+ "from_no": 1,
+ "to_no": 500,
+ "no_of_shares": 500,
+ "rate": 10,
+ "company": "_Test Company",
+ "asset_account": "Cash - _TC",
"equity_or_liability_account": "Creditors - _TC"
},
{
- "doctype" : "Share Transfer",
- "transfer_type" : "Transfer",
- "date" : "2018-01-02",
- "from_shareholder" : "SH-00001",
- "to_shareholder" : "SH-00002",
- "share_type" : "Equity",
- "from_no" : 101,
- "to_no" : 200,
- "no_of_shares" : 100,
- "rate" : 15,
- "company" : "_Test Company",
+ "doctype": "Share Transfer",
+ "transfer_type": "Transfer",
+ "date": "2018-01-02",
+ "from_shareholder": "SH-00001",
+ "to_shareholder": "SH-00002",
+ "share_type": "Equity",
+ "from_no": 101,
+ "to_no": 200,
+ "no_of_shares": 100,
+ "rate": 15,
+ "company": "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
},
{
- "doctype" : "Share Transfer",
- "transfer_type" : "Transfer",
- "date" : "2018-01-03",
- "from_shareholder" : "SH-00001",
- "to_shareholder" : "SH-00003",
- "share_type" : "Equity",
- "from_no" : 201,
- "to_no" : 500,
- "no_of_shares" : 300,
- "rate" : 20,
- "company" : "_Test Company",
+ "doctype": "Share Transfer",
+ "transfer_type": "Transfer",
+ "date": "2018-01-03",
+ "from_shareholder": "SH-00001",
+ "to_shareholder": "SH-00003",
+ "share_type": "Equity",
+ "from_no": 201,
+ "to_no": 500,
+ "no_of_shares": 300,
+ "rate": 20,
+ "company": "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
},
{
- "doctype" : "Share Transfer",
- "transfer_type" : "Transfer",
- "date" : "2018-01-04",
- "from_shareholder" : "SH-00003",
- "to_shareholder" : "SH-00002",
- "share_type" : "Equity",
- "from_no" : 201,
- "to_no" : 400,
- "no_of_shares" : 200,
- "rate" : 15,
- "company" : "_Test Company",
+ "doctype": "Share Transfer",
+ "transfer_type": "Transfer",
+ "date": "2018-01-04",
+ "from_shareholder": "SH-00003",
+ "to_shareholder": "SH-00002",
+ "share_type": "Equity",
+ "from_no": 201,
+ "to_no": 400,
+ "no_of_shares": 200,
+ "rate": 15,
+ "company": "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
},
{
- "doctype" : "Share Transfer",
- "transfer_type" : "Purchase",
- "date" : "2018-01-05",
- "from_shareholder" : "SH-00003",
- "share_type" : "Equity",
- "from_no" : 401,
- "to_no" : 500,
- "no_of_shares" : 100,
- "rate" : 25,
- "company" : "_Test Company",
- "asset_account" : "Cash - _TC",
+ "doctype": "Share Transfer",
+ "transfer_type": "Purchase",
+ "date": "2018-01-05",
+ "from_shareholder": "SH-00003",
+ "share_type": "Equity",
+ "from_no": 401,
+ "to_no": 500,
+ "no_of_shares": 100,
+ "rate": 25,
+ "company": "_Test Company",
+ "asset_account": "Cash - _TC",
"equity_or_liability_account": "Creditors - _TC"
}
]
@@ -91,33 +91,33 @@ class TestShareTransfer(unittest.TestCase):
def test_invalid_share_transfer(self):
doc = frappe.get_doc({
- "doctype" : "Share Transfer",
- "transfer_type" : "Transfer",
- "date" : "2018-01-05",
- "from_shareholder" : "SH-00003",
- "to_shareholder" : "SH-00002",
- "share_type" : "Equity",
- "from_no" : 1,
- "to_no" : 100,
- "no_of_shares" : 100,
- "rate" : 15,
- "company" : "_Test Company",
+ "doctype": "Share Transfer",
+ "transfer_type": "Transfer",
+ "date": "2018-01-05",
+ "from_shareholder": "SH-00003",
+ "to_shareholder": "SH-00002",
+ "share_type": "Equity",
+ "from_no": 1,
+ "to_no": 100,
+ "no_of_shares": 100,
+ "rate": 15,
+ "company": "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
})
self.assertRaises(ShareDontExists, doc.insert)
doc = frappe.get_doc({
- "doctype" : "Share Transfer",
- "transfer_type" : "Purchase",
- "date" : "2018-01-02",
- "from_shareholder" : "SH-00001",
- "share_type" : "Equity",
- "from_no" : 1,
- "to_no" : 200,
- "no_of_shares" : 200,
- "rate" : 15,
- "company" : "_Test Company",
- "asset_account" : "Cash - _TC",
+ "doctype": "Share Transfer",
+ "transfer_type": "Purchase",
+ "date": "2018-01-02",
+ "from_shareholder": "SH-00001",
+ "share_type": "Equity",
+ "from_no": 1,
+ "to_no": 200,
+ "no_of_shares": 200,
+ "rate": 15,
+ "company": "_Test Company",
+ "asset_account": "Cash - _TC",
"equity_or_liability_account": "Creditors - _TC"
})
self.assertRaises(ShareDontExists, doc.insert)
diff --git a/erpnext/accounts/doctype/shareholder/shareholder.json b/erpnext/accounts/doctype/shareholder/shareholder.json
index 873a3e76a3f..e94aea94b75 100644
--- a/erpnext/accounts/doctype/shareholder/shareholder.json
+++ b/erpnext/accounts/doctype/shareholder/shareholder.json
@@ -1,587 +1,163 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2017-12-25 16:50:53.878430",
- "custom": 0,
- "description": "",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "autoname": "naming_series:",
+ "creation": "2017-12-25 16:50:53.878430",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "column_break_2",
+ "naming_series",
+ "section_break_2",
+ "folio_no",
+ "column_break_4",
+ "company",
+ "is_company",
+ "address_contacts",
+ "address_html",
+ "column_break_9",
+ "contact_html",
+ "section_break_3",
+ "share_balance",
+ "contact_list"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "label": "Title",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "options": "ACC-SH-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "options": "ACC-SH-.YYYY.-"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_2",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_2",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "folio_no",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Folio no.",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "fieldname": "folio_no",
+ "fieldtype": "Data",
+ "label": "Folio no.",
+ "read_only": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_4",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "is_company",
- "fieldtype": "Check",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Is Company",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "is_company",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Is Company",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_contacts",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Address and Contacts",
- "length": 0,
- "no_copy": 0,
- "options": "fa fa-map-marker",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "address_contacts",
+ "fieldtype": "Section Break",
+ "label": "Address and Contacts",
+ "options": "fa fa-map-marker"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_html",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Address HTML",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "address_html",
+ "fieldtype": "HTML",
+ "label": "Address HTML",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_9",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_html",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact HTML",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "contact_html",
+ "fieldtype": "HTML",
+ "label": "Contact HTML",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_3",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Share Balance",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_3",
+ "fieldtype": "Section Break",
+ "label": "Share Balance"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "share_balance",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Share Balance",
- "length": 0,
- "no_copy": 0,
- "options": "Share Balance",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "share_balance",
+ "fieldtype": "Table",
+ "label": "Share Balance",
+ "options": "Share Balance",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Hidden list maintaining the list of contacts linked to Shareholder",
- "fieldname": "contact_list",
- "fieldtype": "Code",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact List",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "description": "Hidden list maintaining the list of contacts linked to Shareholder",
+ "fieldname": "contact_list",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Contact List",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-09-18 14:14:24.953014",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Shareholder",
- "name_case": "Title Case",
- "owner": "Administrator",
+ ],
+ "modified": "2019-11-17 23:24:11.395882",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Shareholder",
+ "name_case": "Title Case",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "folio_no",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "title",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "search_fields": "folio_no",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "title",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
index a20f5c08726..8c4efbebe88 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
@@ -70,7 +70,7 @@ class ShippingRule(Document):
def get_shipping_amount_from_rules(self, value):
for condition in self.get("conditions"):
- if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)):
+ if not condition.to_value or (flt(condition.from_value) <= flt(value) <= flt(condition.to_value)):
return condition.shipping_amount
return 0.0
diff --git a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py
index 582ecb2e165..abc6ab82d3d 100644
--- a/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py
@@ -14,13 +14,13 @@ class TestShippingRule(unittest.TestCase):
shipping_rule.name = test_records[0].get('name')
shipping_rule.get("conditions")[0].from_value = 101
self.assertRaises(FromGreaterThanToError, shipping_rule.insert)
-
+
def test_many_zero_to_values(self):
shipping_rule = frappe.copy_doc(test_records[0])
shipping_rule.name = test_records[0].get('name')
shipping_rule.get("conditions")[0].to_value = 0
self.assertRaises(ManyBlankToValuesError, shipping_rule.insert)
-
+
def test_overlapping_conditions(self):
for range_a, range_b in [
((50, 150), (0, 100)),
@@ -38,6 +38,10 @@ class TestShippingRule(unittest.TestCase):
self.assertRaises(OverlappingConditionError, shipping_rule.insert)
def create_shipping_rule(shipping_rule_type, shipping_rule_name):
+
+ if frappe.db.exists("Shipping Rule", shipping_rule_name):
+ return frappe.get_doc("Shipping Rule", shipping_rule_name)
+
sr = frappe.new_doc("Shipping Rule")
sr.account = "_Test Account Shipping Charges - _TC"
sr.calculate_based_on = "Net Total"
@@ -70,4 +74,4 @@ def create_shipping_rule(shipping_rule_type, shipping_rule_name):
})
sr.insert(ignore_permissions=True)
sr.submit()
- return sr
+ return sr
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index f13ca4c49e8..54827503759 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -338,6 +338,16 @@ class Subscription(Document):
# Check invoice dates and make sure it doesn't have outstanding invoices
return getdate(nowdate()) >= getdate(self.current_invoice_start) and not self.has_outstanding_invoice()
+
+ def is_current_invoice_paid(self):
+ if self.is_new_subscription():
+ return False
+
+ last_invoice = frappe.get_doc('Sales Invoice', self.invoices[-1].invoice)
+ if getdate(last_invoice.posting_date) == getdate(self.current_invoice_start) and last_invoice.status == 'Paid':
+ return True
+
+ return False
def process_for_active(self):
"""
@@ -348,7 +358,7 @@ class Subscription(Document):
2. Change the `Subscription` status to 'Past Due Date'
3. Change the `Subscription` status to 'Cancelled'
"""
- if self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice():
+ if not self.is_current_invoice_paid() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()):
self.generate_invoice()
if self.current_invoice_is_past_due():
self.status = 'Past Due Date'
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 5c9e93d019e..bb1b7e392dc 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -3,8 +3,9 @@
from __future__ import unicode_literals
import frappe, erpnext
-from frappe.utils import flt, cstr, cint
+from frappe.utils import flt, cstr, cint, comma_and
from frappe import _
+from erpnext.accounts.utils import get_stock_and_account_balance
from frappe.model.meta import get_field_precision
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
@@ -12,6 +13,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
class ClosedAccountingPeriod(frappe.ValidationError): pass
class StockAccountInvalidTransaction(frappe.ValidationError): pass
+class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
if gl_map:
@@ -88,8 +90,12 @@ def merge_similar_entries(gl_map):
else:
merged_gl_map.append(entry)
+ company = gl_map[0].company if gl_map else erpnext.get_default_company()
+ company_currency = erpnext.get_company_currency(company)
+ precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency)
+
# filter zero debit and credit entries
- merged_gl_map = filter(lambda x: flt(x.debit, 9)!=0 or flt(x.credit, 9)!=0, merged_gl_map)
+ merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map)
merged_gl_map = list(merged_gl_map)
return merged_gl_map
@@ -115,11 +121,9 @@ def check_if_in_list(gle, gl_map, dimensions=None):
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
- validate_account_for_perpetual_inventory(gl_map)
validate_cwip_accounts(gl_map)
round_off_debit_credit(gl_map)
-
for entry in gl_map:
make_entry(entry, adv_adj, update_outstanding, from_repost)
@@ -127,6 +131,10 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
validate_expense_against_budget(entry)
+ if not from_repost:
+ validate_account_for_perpetual_inventory(gl_map)
+
+
def make_entry(args, adv_adj, update_outstanding, from_repost=False):
args.update({"doctype": "GL Entry"})
gle = frappe.get_doc(args)
@@ -137,25 +145,67 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
gle.submit()
def validate_account_for_perpetual_inventory(gl_map):
- if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)) \
- and gl_map[0].voucher_type=="Journal Entry":
- aii_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
- where account_type = 'Stock' and is_group=0""")]
+ if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)):
+ account_list = [gl_entries.account for gl_entries in gl_map]
- for entry in gl_map:
- if entry.account in aii_accounts:
+ aii_accounts = [d.name for d in frappe.get_all("Account",
+ filters={'account_type': 'Stock', 'is_group': 0, 'company': gl_map[0].company})]
+
+ for account in account_list:
+ if account not in aii_accounts:
+ continue
+
+ account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
+ gl_map[0].posting_date, gl_map[0].company)
+
+ if gl_map[0].voucher_type=="Journal Entry":
+ # In case of Journal Entry, there are no corresponding SL entries,
+ # hence deducting currency amount
+ account_bal -= flt(gl_map[0].debit) - flt(gl_map[0].credit)
+ if account_bal == stock_bal:
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
- .format(entry.account), StockAccountInvalidTransaction)
+ .format(account), StockAccountInvalidTransaction)
+
+ # This has been comment for a temporary, will add this code again on release of immutable ledger
+ # elif account_bal != stock_bal:
+ # precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
+ # currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
+
+ # diff = flt(stock_bal - account_bal, precision)
+ # error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses.").format(
+ # stock_bal, account_bal, frappe.bold(account))
+ # error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
+ # stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
+
+ # db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
+ # db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
+
+ # journal_entry_args = {
+ # 'accounts':[
+ # {'account': account, db_or_cr_warehouse_account : abs(diff)},
+ # {'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff) }]
+ # }
+
+ # frappe.msgprint(msg="""{0}
{1}
""".format(error_reason, error_resolution),
+ # raise_exception=StockValueAndAccountBalanceOutOfSync,
+ # title=_('Values Out Of Sync'),
+ # primary_action={
+ # 'label': _('Make Journal Entry'),
+ # 'client_action': 'erpnext.route_to_adjustment_jv',
+ # 'args': journal_entry_args
+ # })
def validate_cwip_accounts(gl_map):
- if not cint(frappe.db.get_value("Asset Settings", None, "disable_cwip_accounting")) \
- and gl_map[0].voucher_type == "Journal Entry":
+ cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
+
+ if cwip_enabled and gl_map[0].voucher_type == "Journal Entry":
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
where account_type = 'Capital Work in Progress' and is_group=0""")]
for entry in gl_map:
if entry.account in cwip_accounts:
- frappe.throw(_("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
+ frappe.throw(
+ _("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
def round_off_debit_credit(gl_map):
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
index 6eafa0d2310..efc76f9158b 100644
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
+++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
@@ -139,15 +139,11 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
}
make() {
- const me = this;
- frappe.upload.make({
- args: {
- method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
- allow_multiple: 0
- },
- no_socketio: true,
- sample_url: "e.g. http://example.com/somefile.csv",
- callback: function(attachment, r) {
+ const me = this;
+ new frappe.ui.FileUploader({
+ method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
+ allow_multiple: 0,
+ on_success: function(attachment, r) {
if (!r.exc && r.message) {
me.data = r.message;
me.setup_transactions_dom();
@@ -533,9 +529,16 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
frappe.db.get_doc(dt, event.value)
.then(doc => {
let displayed_docs = []
+ let payment = []
if (dt === "Payment Entry") {
payment.currency = doc.payment_type == "Receive" ? doc.paid_to_account_currency : doc.paid_from_account_currency;
payment.doctype = dt
+ payment.posting_date = doc.posting_date;
+ payment.party = doc.party;
+ payment.reference_no = doc.reference_no;
+ payment.reference_date = doc.reference_date;
+ payment.paid_amount = doc.paid_amount;
+ payment.name = doc.name;
displayed_docs.push(payment);
} else if (dt === "Journal Entry") {
doc.accounts.forEach(payment => {
@@ -568,11 +571,11 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper;
details_wrapper.append(frappe.render_template("linked_payment_header"));
- displayed_docs.forEach(values => {
- details_wrapper.append(frappe.render_template("linked_payment_row", values));
+ displayed_docs.forEach(payment => {
+ details_wrapper.append(frappe.render_template("linked_payment_row", payment));
})
})
}
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
index bd4b4d7e0b1..69f9907a8d8 100644
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
+++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
@@ -18,6 +18,10 @@ def reconcile(bank_transaction, payment_doctype, payment_name):
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name))
+ if payment_doctype == "Payment Entry" and payment_entry.unallocated_amount > transaction.unallocated_amount:
+ frappe.throw(_("The unallocated amount of Payment Entry {0} \
+ is greater than the Bank Transaction's unallocated amount").format(payment_name))
+
if transaction.unallocated_amount == 0:
frappe.throw(_("This bank transaction is already fully reconciled"))
@@ -373,4 +377,4 @@ def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters):
'start': start,
'page_len': page_len
}
- )
\ No newline at end of file
+ )
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 59936d5116d..156f2181b87 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -23,7 +23,7 @@ class DuplicatePartyAccountError(frappe.ValidationError): pass
@frappe.whitelist()
def get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False, fetch_payment_terms_template=True,
- party_address=None, shipping_address=None, pos_profile=None):
+ party_address=None, company_address=None, shipping_address=None, pos_profile=None):
if not party:
return {}
@@ -31,14 +31,14 @@ def get_party_details(party=None, account=None, party_type="Customer", company=N
frappe.throw(_("{0}: {1} does not exists").format(party_type, party))
return _get_party_details(party, account, party_type,
company, posting_date, bill_date, price_list, currency, doctype, ignore_permissions,
- fetch_payment_terms_template, party_address, shipping_address, pos_profile)
+ fetch_payment_terms_template, party_address, company_address, shipping_address, pos_profile)
def _get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False,
- fetch_payment_terms_template=True, party_address=None, shipping_address=None, pos_profile=None):
+ fetch_payment_terms_template=True, party_address=None, company_address=None,shipping_address=None, pos_profile=None):
- out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
- party = out[party_type.lower()]
+ party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
+ party = party_details[party_type.lower()]
if not ignore_permissions and not frappe.has_permission(party_type, "read", party):
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
@@ -46,76 +46,81 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
party = frappe.get_doc(party_type, party)
currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
- party_address, shipping_address = set_address_details(out, party, party_type, doctype, company, party_address, shipping_address)
- set_contact_details(out, party, party_type)
- set_other_values(out, party, party_type)
- set_price_list(out, party, party_type, price_list, pos_profile)
+ party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
+ set_contact_details(party_details, party, party_type)
+ set_other_values(party_details, party, party_type)
+ set_price_list(party_details, party, party_type, price_list, pos_profile)
- out["tax_category"] = get_address_tax_category(party.get("tax_category"),
+ party_details["tax_category"] = get_address_tax_category(party.get("tax_category"),
party_address, shipping_address if party_type != "Supplier" else party_address)
- out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company,
- customer_group=out.customer_group, supplier_group=out.supplier_group, tax_category=out.tax_category,
- billing_address=party_address, shipping_address=shipping_address)
+
+ if not party_details.get("taxes_and_charges"):
+ party_details["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company,
+ customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
+ billing_address=party_address, shipping_address=shipping_address)
if fetch_payment_terms_template:
- out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
+ party_details["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company)
- if not out.get("currency"):
- out["currency"] = currency
+ if not party_details.get("currency"):
+ party_details["currency"] = currency
# sales team
if party_type=="Customer":
- out["sales_team"] = [{
+ party_details["sales_team"] = [{
"sales_person": d.sales_person,
"allocated_percentage": d.allocated_percentage or None
} for d in party.get("sales_team")]
# supplier tax withholding category
if party_type == "Supplier" and party:
- out["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
+ party_details["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
- return out
+ return party_details
-def set_address_details(out, party, party_type, doctype=None, company=None, party_address=None, shipping_address=None):
+def set_address_details(party_details, party, party_type, doctype=None, company=None, party_address=None, company_address=None, shipping_address=None):
billing_address_field = "customer_address" if party_type == "Lead" \
else party_type.lower() + "_address"
- out[billing_address_field] = party_address or get_default_address(party_type, party.name)
+ party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
if doctype:
- out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field]))
+ party_details.update(get_fetch_values(doctype, billing_address_field, party_details[billing_address_field]))
# address display
- out.address_display = get_address_display(out[billing_address_field])
+ party_details.address_display = get_address_display(party_details[billing_address_field])
# shipping address
if party_type in ["Customer", "Lead"]:
- out.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name)
- out.shipping_address = get_address_display(out["shipping_address_name"])
+ party_details.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name)
+ party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
if doctype:
- out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
+ party_details.update(get_fetch_values(doctype, 'shipping_address_name', party_details.shipping_address_name))
- if doctype and doctype in ['Delivery Note', 'Sales Invoice']:
- out.update(get_company_address(company))
- if out.company_address:
- out.update(get_fetch_values(doctype, 'company_address', out.company_address))
- get_regional_address_details(out, doctype, company)
+ if company_address:
+ party_details.update({'company_address': company_address})
+ else:
+ party_details.update(get_company_address(company))
- elif doctype and doctype == "Purchase Invoice":
- out.update(get_company_address(company))
- if out.company_address:
- out["shipping_address"] = shipping_address or out["company_address"]
- out.shipping_address_display = get_address_display(out["shipping_address"])
- out.update(get_fetch_values(doctype, 'shipping_address', out.shipping_address))
- get_regional_address_details(out, doctype, company)
+ if doctype and doctype in ['Delivery Note', 'Sales Invoice', 'Sales Order']:
+ if party_details.company_address:
+ party_details.update(get_fetch_values(doctype, 'company_address', party_details.company_address))
+ get_regional_address_details(party_details, doctype, company)
- return out.get(billing_address_field), out.shipping_address_name
+ elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
+ if party_details.company_address:
+ party_details["shipping_address"] = shipping_address or party_details["company_address"]
+ party_details.shipping_address_display = get_address_display(party_details["shipping_address"])
+ party_details.update(get_fetch_values(doctype, 'shipping_address', party_details.shipping_address))
+ get_regional_address_details(party_details, doctype, company)
+
+ return party_details.get(billing_address_field), party_details.shipping_address_name
@erpnext.allow_regional
-def get_regional_address_details(out, doctype, company):
+def get_regional_address_details(party_details, doctype, company):
pass
-def set_contact_details(out, party, party_type):
- out.contact_person = get_default_contact(party_type, party.name)
+def set_contact_details(party_details, party, party_type):
+ party_details.contact_person = get_default_contact(party_type, party.name)
- if not out.contact_person:
- out.update({
+ if not party_details.contact_person:
+ party_details.update({
"contact_person": None,
"contact_display": None,
"contact_email": None,
@@ -125,22 +130,22 @@ def set_contact_details(out, party, party_type):
"contact_department": None
})
else:
- out.update(get_contact_details(out.contact_person))
+ party_details.update(get_contact_details(party_details.contact_person))
-def set_other_values(out, party, party_type):
+def set_other_values(party_details, party, party_type):
# copy
if party_type=="Customer":
to_copy = ["customer_name", "customer_group", "territory", "language"]
else:
to_copy = ["supplier_name", "supplier_group", "language"]
for f in to_copy:
- out[f] = party.get(f)
+ party_details[f] = party.get(f)
# fields prepended with default in Customer doctype
for f in ['currency'] \
+ (['sales_partner', 'commission_rate'] if party_type=="Customer" else []):
if party.get("default_" + f):
- out[f] = party.get("default_" + f)
+ party_details[f] = party.get("default_" + f)
def get_default_price_list(party):
"""Return default price list for party (Document object)"""
@@ -155,7 +160,7 @@ def get_default_price_list(party):
return None
-def set_price_list(out, party, party_type, given_price_list, pos=None):
+def set_price_list(party_details, party, party_type, given_price_list, pos=None):
# price list
price_list = get_permitted_documents('Price List')
@@ -173,9 +178,9 @@ def set_price_list(out, party, party_type, given_price_list, pos=None):
price_list = get_default_price_list(party) or given_price_list
if price_list:
- out.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True)
+ party_details.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True)
- out["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list
+ party_details["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list
def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype):
diff --git a/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html b/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html
index 69e42c3bdbc..6fe69990513 100644
--- a/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html
+++ b/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html
@@ -49,7 +49,7 @@
{% endfor %}
\n\t{{ doc.company }}
\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
\n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
\n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n\t{% endif %}\n
\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{{ _(\"Customer\") }}:
\n\t\t{{ doc.customer_name }}
\n\t\t{{ customer_address }}\n\t{% endif %}\n
| {{ _(\"Item\") }} | \n\t\t\t{{ _(\"Qty\") }} | \n\t\t\t{{ _(\"Amount\") }} | \n\t\t
|---|---|---|
| \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t {{ _(\"HSN/SAC\") }}: {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t {{ _(\"Serial No\") }}: {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t | \n\t\t\t{{ item.qty }} @ {{ item.rate }} | \n\t\t\t{{ item.get_formatted(\"amount\") }} | \n\t\t
| \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t | \n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t | \n\t\t\t{% endif %}\n\t\t
| \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t | \n\t\t\t||
| \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t | \n\t\t
{{ doc.terms or \"\" }}
\n{{ _(\"Thank you, please visit again.\") }}
", - "idx": 0, - "line_breaks": 0, - "modified": "2019-01-24 17:09:27.190929", - "modified_by": "Administrator", - "module": "Accounts", - "name": "GST POS Invoice", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Server", - "show_section_headings": 0, + "align_labels_right": 0, + "creation": "2017-08-08 12:33:04.773099", + "custom_format": 1, + "disabled": 0, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n\t{{ doc.company }}
\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
\n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
\n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n\t{% endif %}\n
\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{{ _(\"Customer\") }}:
\n\t\t{{ doc.customer_name }}
\n\t\t{{ customer_address }}\n\t{% endif %}\n
| {{ _(\"Item\") }} | \n\t\t\t{{ _(\"Qty\") }} | \n\t\t\t{{ _(\"Amount\") }} | \n\t\t
|---|---|---|
| \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t {{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t {{ _(\"HSN/SAC\") }}: {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t {{ _(\"Serial No\") }}: {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t | \n\t\t\t{{ item.qty }} @ {{ item.rate }} | \n\t\t\t{{ item.get_formatted(\"amount\") }} | \n\t\t
| \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t | \n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t | \n\t\t\t{% endif %}\n\t\t
| \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t | \n\t\t\t||
| \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t | \n\t\t
{{ doc.terms or \"\" }}
\n{{ _(\"Thank you, please visit again.\") }}
", + "idx": 0, + "line_breaks": 0, + "modified": "2019-12-09 17:39:23.356573", + "modified_by": "Administrator", + "module": "Accounts", + "name": "GST POS Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/print_format/pos_invoice/pos_invoice.json b/erpnext/accounts/print_format/pos_invoice/pos_invoice.json index c3450d6a73c..be699228c52 100644 --- a/erpnext/accounts/print_format/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/print_format/pos_invoice/pos_invoice.json @@ -1,21 +1,22 @@ { - "align_labels_right": 0, - "creation": "2011-12-21 11:08:55", - "custom_format": 1, - "disabled": 0, - "doc_type": "Sales Invoice", - "docstatus": 0, - "doctype": "Print Format", - "html": "\n\n\n\t{{ doc.company }}
\n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \" + (doc.select_print_heading or _(\"Invoice\")) }}
\n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n\t{% endif %}\n
\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}\n
| {{ _(\"Item\") }} | \n\t\t\t{{ _(\"Qty\") }} | \n\t\t\t{{ _(\"Amount\") }} | \n\t\t
|---|---|---|
| \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t {{ item.item_name }}{%- endif -%}\n\t\t\t | \n\t\t\t{{ item.qty }} @ {{ item.get_formatted(\"rate\") }} | \n\t\t\t{{ item.get_formatted(\"amount\") }} | \n\t\t
| \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t | \n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t | \n\t\t\t{% endif %}\n\t\t
| \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t | \n\t\t\t||
| \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t | \n\t\t\t||
| \n\t\t\t\t{{ _(\"Total Qty\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"pos_total_qty\") }}\n\t\t\t | \n\t\t
{{ doc.terms or \"\" }}
\n{{ _(\"Thank you, please visit again.\") }}
", - "idx": 1, - "line_breaks": 0, - "modified": "2018-03-20 14:24:12.394354", - "modified_by": "Administrator", - "module": "Accounts", - "name": "POS Invoice", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Server", - "show_section_headings": 0, + "align_labels_right": 0, + "creation": "2011-12-21 11:08:55", + "custom_format": 1, + "disabled": 0, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n\n\t{{ doc.company }}
\n\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n
\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}\n
| {{ _(\"Item\") }} | \n\t\t\t{{ _(\"Qty\") }} | \n\t\t\t{{ _(\"Amount\") }} | \n\t\t
|---|---|---|
| \n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t {{ item.item_name }}{%- endif -%}\n\t\t\t | \n\t\t\t{{ item.qty }} @ {{ item.get_formatted(\"rate\") }} | \n\t\t\t{{ item.get_formatted(\"amount\") }} | \n\t\t
| \n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t | \n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t | \n\t\t\t{% endif %}\n\t\t
| \n\t\t\t\t\t{{ row.description }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t | \n\t\t\t||
| \n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t | \n\t\t||
| \n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t | \n\t\t\t
{{ doc.terms or \"\" }}
\n{{ _(\"Thank you, please visit again.\") }}
", + "idx": 1, + "line_breaks": 0, + "modified": "2019-12-09 17:40:53.183574", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 8eb670de510..b1f427ca7f6 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -100,6 +100,11 @@ frappe.query_reports["Accounts Payable"] = { "fieldtype": "Link", "options": "Supplier Group" }, + { + "fieldname":"based_on_payment_terms", + "label": __("Based On Payment Terms"), + "fieldtype": "Check", + }, { "fieldname":"tax_id", "label": __("Tax Id"), diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 5f0fdc9f2c7..4a9f1b0dc44 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -88,6 +88,11 @@ frappe.query_reports["Accounts Payable Summary"] = { "label": __("Supplier Group"), "fieldtype": "Link", "options": "Supplier Group" + }, + { + "fieldname":"based_on_payment_terms", + "label": __("Based On Payment Terms"), + "fieldtype": "Check", } ], diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 228be18d219..9b4dda2f696 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -79,13 +79,20 @@ frappe.query_reports["Accounts Receivable"] = { "options": "Customer", on_change: () => { var customer = frappe.query_report.get_filter_value('customer'); + var company = frappe.query_report.get_filter_value('company'); if (customer) { - frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "credit_limit", "payment_terms"], function(value) { + frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "payment_terms"], function(value) { frappe.query_report.set_filter_value('tax_id', value["tax_id"]); frappe.query_report.set_filter_value('customer_name', value["customer_name"]); - frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]); frappe.query_report.set_filter_value('payment_terms', value["payment_terms"]); }); + + frappe.db.get_value('Customer Credit Limit', {'parent': customer, 'company': company}, + ["credit_limit"], function(value) { + if (value) { + frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]); + } + }, "Customer"); } else { frappe.query_report.set_filter_value('tax_id', ""); frappe.query_report.set_filter_value('customer_name', ""); diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index bcbd427186c..f82146a1df8 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -60,6 +60,7 @@ class ReceivablePayableReport(object): def get_data(self): self.get_gl_entries() + self.get_sales_invoices_or_customers_based_on_sales_person() self.voucher_balance = OrderedDict() self.init_voucher_balance() # invoiced, paid, credit_note, outstanding @@ -103,12 +104,18 @@ class ReceivablePayableReport(object): def get_invoices(self, gle): if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'): - self.invoices.add(gle.voucher_no) + if self.filters.get("sales_person"): + if gle.voucher_no in self.sales_person_records.get("Sales Invoice", []) \ + or gle.party in self.sales_person_records.get("Customer", []): + self.invoices.add(gle.voucher_no) + else: + self.invoices.add(gle.voucher_no) def update_voucher_balance(self, gle): # get the row where this balance needs to be updated # if its a payment, it will return the linked invoice or will be considered as advance row = self.get_voucher_balance(gle) + if not row: return # gle_balance will be the total "debit - credit" for receivable type reports and # and vice-versa for payable type reports gle_balance = self.get_gle_balance(gle) @@ -129,8 +136,13 @@ class ReceivablePayableReport(object): row.paid -= gle_balance def get_voucher_balance(self, gle): - voucher_balance = None + if self.filters.get("sales_person"): + against_voucher = gle.against_voucher or gle.voucher_no + if not (gle.party in self.sales_person_records.get("Customer", []) or \ + against_voucher in self.sales_person_records.get("Sales Invoice", [])): + return + voucher_balance = None if gle.against_voucher: # find invoice against_voucher = gle.against_voucher @@ -159,7 +171,7 @@ class ReceivablePayableReport(object): row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision) row.invoice_grand_total = row.invoiced - if abs(row.outstanding) > 0.1/10 ** self.currency_precision: + if abs(row.outstanding) > 1.0/10 ** self.currency_precision: # non-zero oustanding, we must consider this row if self.is_invoice(row) and self.filters.based_on_payment_terms: @@ -188,7 +200,11 @@ class ReceivablePayableReport(object): self.data.append(row) def set_invoice_details(self, row): - row.update(self.invoice_details.get(row.voucher_no, {})) + invoice_details = self.invoice_details.get(row.voucher_no, {}) + if row.due_date: + invoice_details.pop("due_date", None) + row.update(invoice_details) + if row.voucher_type == 'Sales Invoice': if self.filters.show_delivery_notes: self.set_delivery_notes(row) @@ -269,7 +285,7 @@ class ReceivablePayableReport(object): def set_party_details(self, row): # customer / supplier name - party_details = self.get_party_details(row.party) + party_details = self.get_party_details(row.party) or {} row.update(party_details) if self.filters.get(scrub(self.filters.party_type)): row.currency = row.account_currency @@ -314,7 +330,7 @@ class ReceivablePayableReport(object): self.append_payment_term(row, d, term) def append_payment_term(self, row, d, term): - if self.filters.get("customer") and d.currency == d.party_account_currency: + if (self.filters.get("customer") or self.filters.get("supplier")) and d.currency == d.party_account_currency: invoiced = d.payment_amount else: invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) @@ -508,6 +524,22 @@ class ReceivablePayableReport(object): order by posting_date, party""" .format(select_fields, conditions), values, as_dict=True) + def get_sales_invoices_or_customers_based_on_sales_person(self): + if self.filters.get("sales_person"): + lft, rgt = frappe.db.get_value("Sales Person", + self.filters.get("sales_person"), ["lft", "rgt"]) + + records = frappe.db.sql(""" + select distinct parent, parenttype + from `tabSales Team` steam + where parenttype in ('Customer', 'Sales Invoice') + and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person) + """, (lft, rgt), as_dict=1) + + self.sales_person_records = frappe._dict() + for d in records: + self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent) + def prepare_conditions(self): conditions = [""] values = [self.party_type, self.filters.report_date] @@ -560,16 +592,6 @@ class ReceivablePayableReport(object): conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)") values.append(self.filters.get("sales_partner")) - if self.filters.get("sales_person"): - lft, rgt = frappe.db.get_value("Sales Person", - self.filters.get("sales_person"), ["lft", "rgt"]) - - conditions.append("""exists(select name from `tabSales Team` steam where - steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1}) - and ((steam.parent = voucher_no and steam.parenttype = voucher_type) - or (steam.parent = against_voucher and steam.parenttype = against_voucher_type) - or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt)) - def add_supplier_filters(self, conditions, values): if self.filters.get("supplier_group"): conditions.append("""party in (select name from tabSupplier diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index 0120608a8ff..d54824b6855 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -106,6 +106,11 @@ frappe.query_reports["Accounts Receivable Summary"] = { "label": __("Sales Person"), "fieldtype": "Link", "options": "Sales Person" + }, + { + "fieldname":"based_on_payment_terms", + "label": __("Based On Payment Terms"), + "fieldtype": "Check", } ], diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index b90a7a9501b..b607c0f7028 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -36,6 +36,9 @@ class AccountsReceivableSummary(ReceivablePayableReport): self.filters.report_date) or {} for party, party_dict in iteritems(self.party_total): + if party_dict.outstanding == 0: + continue + row = frappe._dict() row.party = party diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index 0c99f1424cf..78546609adb 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -4,126 +4,141 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import formatdate, getdate, flt, add_days +from frappe.utils import formatdate, flt, add_days + def execute(filters=None): filters.day_before_from_date = add_days(filters.from_date, -1) columns, data = get_columns(filters), get_data(filters) return columns, data - + + def get_data(filters): data = [] - + asset_categories = get_asset_categories(filters) assets = get_assets(filters) - asset_costs = get_asset_costs(assets, filters) - asset_depreciations = get_accumulated_depreciations(assets, filters) - + for asset_category in asset_categories: row = frappe._dict() - row.asset_category = asset_category - row.update(asset_costs.get(asset_category)) + # row.asset_category = asset_category + row.update(asset_category) + + row.cost_as_on_to_date = (flt(row.cost_as_on_from_date) + flt(row.cost_of_new_purchase) - + flt(row.cost_of_sold_asset) - flt(row.cost_of_scrapped_asset)) + + row.update(next(asset for asset in assets if asset["asset_category"] == asset_category.get("asset_category", ""))) + row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) + + flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated)) + + row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) - + flt(row.accumulated_depreciation_as_on_from_date)) + + row.net_asset_value_as_on_to_date = (flt(row.cost_as_on_to_date) - + flt(row.accumulated_depreciation_as_on_to_date)) - row.cost_as_on_to_date = (flt(row.cost_as_on_from_date) + flt(row.cost_of_new_purchase) - - flt(row.cost_of_sold_asset) - flt(row.cost_of_scrapped_asset)) - - row.update(asset_depreciations.get(asset_category)) - row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) + - flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated)) - - row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) - - flt(row.accumulated_depreciation_as_on_from_date)) - - row.net_asset_value_as_on_to_date = (flt(row.cost_as_on_to_date) - - flt(row.accumulated_depreciation_as_on_to_date)) - data.append(row) - + return data - + + def get_asset_categories(filters): - return frappe.db.sql_list(""" - select distinct asset_category from `tabAsset` - where docstatus=1 and company=%s and purchase_date <= %s - """, (filters.company, filters.to_date)) - + return frappe.db.sql(""" + SELECT asset_category, + ifnull(sum(case when purchase_date < %(from_date)s then + case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then + gross_purchase_amount + else + 0 + end + else + 0 + end), 0) as cost_as_on_from_date, + ifnull(sum(case when purchase_date >= %(from_date)s then + gross_purchase_amount + else + 0 + end), 0) as cost_of_new_purchase, + ifnull(sum(case when ifnull(disposal_date, 0) != 0 + and disposal_date >= %(from_date)s + and disposal_date <= %(to_date)s then + case when status = "Sold" then + gross_purchase_amount + else + 0 + end + else + 0 + end), 0) as cost_of_sold_asset, + ifnull(sum(case when ifnull(disposal_date, 0) != 0 + and disposal_date >= %(from_date)s + and disposal_date <= %(to_date)s then + case when status = "Scrapped" then + gross_purchase_amount + else + 0 + end + else + 0 + end), 0) as cost_of_scrapped_asset + from `tabAsset` + where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s + group by asset_category + """, {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) + + def get_assets(filters): return frappe.db.sql(""" - select name, asset_category, purchase_date, gross_purchase_amount, disposal_date, status - from `tabAsset` - where docstatus=1 and company=%s and purchase_date <= %s""", - (filters.company, filters.to_date), as_dict=1) - -def get_asset_costs(assets, filters): - asset_costs = frappe._dict() - for d in assets: - asset_costs.setdefault(d.asset_category, frappe._dict({ - "cost_as_on_from_date": 0, - "cost_of_new_purchase": 0, - "cost_of_sold_asset": 0, - "cost_of_scrapped_asset": 0 - })) - - costs = asset_costs[d.asset_category] - - if getdate(d.purchase_date) < getdate(filters.from_date): - if not d.disposal_date or getdate(d.disposal_date) >= getdate(filters.from_date): - costs.cost_as_on_from_date += flt(d.gross_purchase_amount) - else: - costs.cost_of_new_purchase += flt(d.gross_purchase_amount) - - if d.disposal_date and getdate(d.disposal_date) >= getdate(filters.from_date) \ - and getdate(d.disposal_date) <= getdate(filters.to_date): - if d.status == "Sold": - costs.cost_of_sold_asset += flt(d.gross_purchase_amount) - elif d.status == "Scrapped": - costs.cost_of_scrapped_asset += flt(d.gross_purchase_amount) - - return asset_costs - -def get_accumulated_depreciations(assets, filters): - asset_depreciations = frappe._dict() - for d in assets: - asset = frappe.get_doc("Asset", d.name) - - if d.asset_category in asset_depreciations: - asset_depreciations[d.asset_category]['accumulated_depreciation_as_on_from_date'] += asset.opening_accumulated_depreciation - else: - asset_depreciations.setdefault(d.asset_category, frappe._dict({ - "accumulated_depreciation_as_on_from_date": asset.opening_accumulated_depreciation, - "depreciation_amount_during_the_period": 0, - "depreciation_eliminated_during_the_period": 0 - })) + SELECT results.asset_category, + sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date, + sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, + sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period + from (SELECT a.asset_category, + ifnull(sum(a.opening_accumulated_depreciation + + case when ds.schedule_date < %(from_date)s and + (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then + ds.depreciation_amount + else + 0 + end), 0) as accumulated_depreciation_as_on_from_date, + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s and ds.schedule_date <= a.disposal_date then + ds.depreciation_amount + else + 0 + end), 0) as depreciation_eliminated_during_the_period, - depr = asset_depreciations[d.asset_category] + ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s + and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then + ds.depreciation_amount + else + 0 + end), 0) as depreciation_amount_during_the_period + from `tabAsset` a, `tabDepreciation Schedule` ds + where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent + group by a.asset_category + union + SELECT a.asset_category, + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then + 0 + else + a.opening_accumulated_depreciation + end), 0) as accumulated_depreciation_as_on_from_date, + ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then + a.opening_accumulated_depreciation + else + 0 + end), 0) as depreciation_eliminated_during_the_period, + 0 as depreciation_amount_during_the_period + from `tabAsset` a + where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s + and not exists(select * from `tabDepreciation Schedule` ds where a.name = ds.parent) + group by a.asset_category) as results + group by results.asset_category + """, {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) - if not asset.schedules: # if no schedule, - if asset.disposal_date: - # and disposal is NOT within the period, then opening accumulated depreciation not included - if getdate(asset.disposal_date) < getdate(filters.from_date) or getdate(asset.disposal_date) > getdate(filters.to_date): - asset_depreciations[d.asset_category]['accumulated_depreciation_as_on_from_date'] = 0 - # if no schedule, and disposal is within period, accumulated dep is the amount eliminated - if getdate(asset.disposal_date) >= getdate(filters.from_date) and getdate(asset.disposal_date) <= getdate(filters.to_date): - depr.depreciation_eliminated_during_the_period += asset.opening_accumulated_depreciation - - for schedule in asset.get("schedules"): - if getdate(schedule.schedule_date) < getdate(filters.from_date): - if not asset.disposal_date or getdate(asset.disposal_date) >= getdate(filters.from_date): - depr.accumulated_depreciation_as_on_from_date += flt(schedule.depreciation_amount) - elif getdate(schedule.schedule_date) <= getdate(filters.to_date): - if not asset.disposal_date: - depr.depreciation_amount_during_the_period += flt(schedule.depreciation_amount) - else: - if getdate(schedule.schedule_date) <= getdate(asset.disposal_date): - depr.depreciation_amount_during_the_period += flt(schedule.depreciation_amount) - - if asset.disposal_date and getdate(asset.disposal_date) >= getdate(filters.from_date) and getdate(asset.disposal_date) <= getdate(filters.to_date): - if getdate(schedule.schedule_date) <= getdate(asset.disposal_date): - depr.depreciation_eliminated_during_the_period += flt(schedule.depreciation_amount) - - return asset_depreciations - def get_columns(filters): return [ { diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index 4bc29da2c7d..8c11514aa64 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -2,7 +2,7 @@ // License: GNU General Public License v3. See license.txt frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Balance Sheet"] = erpnext.financial_statements; + frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statements); frappe.query_reports["Balance Sheet"]["filters"].push({ "fieldname": "accumulated_values", diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js index 24511871fdb..3ec4d306c35 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js @@ -46,13 +46,24 @@ frappe.query_reports["Budget Variance Report"] = { fieldtype: "Select", options: ["Cost Center", "Project"], default: "Cost Center", - reqd: 1 + reqd: 1, + on_change: function() { + frappe.query_report.set_filter_value("budget_against_filter", []); + frappe.query_report.refresh(); + } }, { - fieldname: "cost_center", - label: __("Cost Center"), - fieldtype: "Link", - options: "Cost Center" + fieldname:"budget_against_filter", + label: __('Dimension Filter'), + fieldtype: "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let budget_against = frappe.query_report.get_filter_value('budget_against'); + if (!budget_against) return; + + return frappe.db.get_link_options(budget_against, txt); + } }, { fieldname:"show_cumulative", diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 8d65ac87148..39e218bfad2 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -12,22 +12,22 @@ from six import iteritems from pprint import pprint def execute(filters=None): if not filters: filters = {} - validate_filters(filters) + columns = get_columns(filters) - if filters.get("cost_center"): - cost_centers = [filters.get("cost_center")] + if filters.get("budget_against_filter"): + dimensions = filters.get("budget_against_filter") else: - cost_centers = get_cost_centers(filters) + dimensions = get_cost_centers(filters) period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"]) - cam_map = get_cost_center_account_month_map(filters) + cam_map = get_dimension_account_month_map(filters) data = [] - for cost_center in cost_centers: - cost_center_items = cam_map.get(cost_center) - if cost_center_items: - for account, monthwise_data in iteritems(cost_center_items): - row = [cost_center, account] + for dimension in dimensions: + dimension_items = cam_map.get(dimension) + if dimension_items: + for account, monthwise_data in iteritems(dimension_items): + row = [dimension, account] totals = [0, 0, 0] for year in get_fiscal_years(filters): last_total = 0 @@ -55,10 +55,6 @@ def execute(filters=None): return columns, data -def validate_filters(filters): - if filters.get("budget_against") != "Cost Center" and filters.get("cost_center"): - frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center")) - def get_columns(filters): columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"] @@ -98,11 +94,12 @@ def get_cost_centers(filters): else: return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec -#Get cost center & target details -def get_cost_center_target_details(filters): +#Get dimension & target details +def get_dimension_target_details(filters): cond = "" - if filters.get("cost_center"): - cond += " and b.cost_center=%s" % frappe.db.escape(filters.get("cost_center")) + if filters.get("budget_against_filter"): + cond += " and b.{budget_against} in (%s)".format(budget_against = \ + frappe.scrub(filters.get('budget_against'))) % ', '.join(['%s']* len(filters.get('budget_against_filter'))) return frappe.db.sql(""" select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year @@ -110,8 +107,8 @@ def get_cost_center_target_details(filters): where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year """.format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond), - (filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company), as_dict=True) - + tuple([filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company] + filters.get('budget_against_filter')), + as_dict=True) #Get target distribution details of accounts of cost center @@ -153,14 +150,14 @@ def get_actual_details(name, filters): return cc_actual_details -def get_cost_center_account_month_map(filters): +def get_dimension_account_month_map(filters): import datetime - cost_center_target_details = get_cost_center_target_details(filters) + dimension_target_details = get_dimension_target_details(filters) tdd = get_target_distribution_details(filters) cam_map = {} - for ccd in cost_center_target_details: + for ccd in dimension_target_details: actual_details = get_actual_details(ccd.budget_against, filters) for month_id in range(1, 13): diff --git a/erpnext/accounts/report/financial_statements.html b/erpnext/accounts/report/financial_statements.html index 4081723bf0f..50947ecf5ef 100644 --- a/erpnext/accounts/report/financial_statements.html +++ b/erpnext/accounts/report/financial_statements.html @@ -1,5 +1,6 @@ {% var report_columns = report.get_columns_for_print(); + report_columns = report_columns.filter(col => !col.hidden); if (report_columns.length > 8) { frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application.")); @@ -15,34 +16,35 @@ height: 37px; } -{% var letterhead= filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) %} -{% if(letterhead) { %} -| - {% for(var i=2, l=report_columns.length; i |
|---|
Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}
++ Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %} +
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 3c8de6026a6..40d5682726d 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -264,8 +264,8 @@ def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False def add_total_row(out, root_type, balance_must_be, period_list, company_currency): total_row = { - "account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'", - "account": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'", + "account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)), + "account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)), "currency": company_currency } diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index 17da3b915f8..40469aecc1d 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -33,7 +33,7 @@ - {% for(var i=0, l=data.length-1; iNotes:
\n\nbase for using base salary of the EmployeeBS = Basic SalaryEmployment Type = employment_typeBranch = branchPayment Days = payment_daysLeave without pay = leave_without_paybase\nCondition: base < 10000\nFormula: base * .2BS \nCondition: BS > 2000\nFormula: BS * .1employment_type \nCondition: employment_type==\"Intern\"\nAmount: 1000Notes:
\n\nbase for using base salary of the EmployeeBS = Basic SalaryEmployment Type = employment_typeBranch = branchPayment Days = payment_daysLeave without pay = leave_without_paybase\nCondition: base < 10000\nFormula: base * .2BS \nCondition: BS > 2000\nFormula: BS * .1employment_type \nCondition: employment_type==\"Intern\"\nAmount: 1000| + |
+
+ {{_("Training Event:")}} {{ doc.event_name }}
+
+ |
+ + |
{{ doc.introduction }}
- -| + |
+
+ {{ doc.introduction }}
+
+
|
+ + |
{{ doc.introduction }}
\n\n| \n | \n \n {{_(\"Training Event:\")}} {{ doc.event_name }}\n \n | \n \n |
| \n | \n \n \n
| \n \n |
{{ message }}
+| + |
+
+ {{_("Training Event:")}} {{ doc.event_name }}
+
+ |
+ + |
| + |
+
+ {{ doc.introduction }}
+
+
|
+ + |
|
-
+ |
- {%= i+1 %}. {%= addr_list[i].address_type!="Other" ? __(addr_list[i].address_type) : addr_list[i].address_title %} - {% if(addr_list[i].is_primary_address) { %} - ({%= __("Primary") %}){% } %} - {% if(addr_list[i].is_shipping_address) { %} - ({%= __("Shipping") %}){% } %} +
+ {%= i+1 %}. {%= addr_list[i].address_title %}{% if(addr_list[i].address_type!="Other") { %} + ({%= __(addr_list[i].address_type) %}){% } %} + {% if(addr_list[i].is_primary_address) { %} + ({%= __("Primary") %}){% } %} + {% if(addr_list[i].is_shipping_address) { %} + ({%= __("Shipping") %}){% } %} - - {%= __("Edit") %} -
-{%= addr_list[i].display %}
-{%= addr_list[i].display %}
+{%= __("No address added yet.") %}
{% } %} - - + \ No newline at end of file diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index ffc5e6ad365..3f444f83879 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -63,14 +63,33 @@ $.extend(erpnext, { let callback = ''; let on_close = ''; - if (grid_row.doc.serial_no) { - grid_row.doc.has_serial_no = true; - } - - me.show_serial_batch_selector(grid_row.frm, grid_row.doc, - callback, on_close, true); + frappe.model.get_value('Item', {'name':grid_row.doc.item_code}, 'has_serial_no', + (data) => { + if(data) { + grid_row.doc.has_serial_no = data.has_serial_no; + me.show_serial_batch_selector(grid_row.frm, grid_row.doc, + callback, on_close, true); + } + } + ); }); }, + + route_to_adjustment_jv: (args) => { + frappe.model.with_doctype('Journal Entry', () => { + // route to adjustment Journal Entry to handle Account Balance and Stock Value mismatch + let journal_entry = frappe.model.get_new_doc('Journal Entry'); + + args.accounts.forEach((je_account) => { + let child_row = frappe.model.add_child(journal_entry, "accounts"); + child_row.account = je_account.account; + child_row.debit_in_account_currency = je_account.debit_in_account_currency; + child_row.credit_in_account_currency = je_account.credit_in_account_currency; + child_row.party_type = "" ; + }); + frappe.set_route('Form','Journal Entry', journal_entry.name); + }); + } }); @@ -439,7 +458,8 @@ erpnext.utils.update_child_items = function(opts) { fieldname:"item_code", options: 'Item', in_list_view: 1, - read_only: 1, + read_only: 0, + disabled: 0, label: __('Item Code') }, { fieldtype:'Float', @@ -482,6 +502,7 @@ erpnext.utils.update_child_items = function(opts) { frm.doc[opts.child_docname].forEach(d => { dialog.fields_dict.trans_items.df.data.push({ "docname": d.name, + "name": d.name, "item_code": d.item_code, "qty": d.qty, "rate": d.rate, diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index a8d3888ba0f..99c1b8ae8f3 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -7,6 +7,21 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { if(!method) { method = "erpnext.accounts.party.get_party_details"; } + + if (args) { + if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) { + if (frm.doc.company_address && (!args.company_address)) { + args.company_address = frm.doc.company_address; + } + } + + if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) { + if (frm.doc.shipping_address && (!args.shipping_address)) { + args.shipping_address = frm.doc.shipping_address; + } + } + } + if(!args) { if((frm.doctype != "Purchase Order" && frm.doc.customer) || (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) { @@ -30,6 +45,35 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { }; } + if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) { + if (!args) { + args = { + party: frm.doc.customer || frm.doc.party_name, + party_type: 'Customer' + } + } + if (frm.doc.company_address && (!args.company_address)) { + args.company_address = frm.doc.company_address; + } + + if (frm.doc.shipping_address_name &&(!args.shipping_address_name)) { + args.shipping_address_name = frm.doc.shipping_address_name; + } + } + + if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) { + if (!args) { + args = { + party: frm.doc.supplier, + party_type: 'Supplier' + } + } + + if (frm.doc.shipping_address && (!args.shipping_address)) { + args.shipping_address = frm.doc.shipping_address; + } + } + if (args) { args.posting_date = frm.doc.posting_date || frm.doc.transaction_date; } diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 41a59d0af5d..e64d5458b30 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -139,7 +139,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.dialog.set_value('serial_no', d.serial_no); } - if (d.batch_no) { + if (d.has_batch_no && d.batch_no) { this.frm.doc.items.forEach(data => { if(data.item_code == d.item_code) { this.dialog.fields_dict.batches.df.data.push({ @@ -389,12 +389,14 @@ erpnext.SerialNoBatchSelector = Class.extend({ let serial_no_filters = { item_code: me.item_code, + batch_no: this.doc.batch_no || null, delivery_document_no: "" } if (me.warehouse_details.name) { serial_no_filters['warehouse'] = me.warehouse_details.name; } + return [ {fieldtype: 'Section Break', label: __('Serial Numbers')}, { diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/less/erpnext.less index 8ed5f1adb05..abe48685f03 100644 --- a/erpnext/public/less/erpnext.less +++ b/erpnext/public/less/erpnext.less @@ -426,7 +426,7 @@ body[data-route="pos"] { .collapse-btn { cursor: pointer; } - + @media (max-width: @screen-xs) { .page-actions { max-width: 110px; diff --git a/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json b/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json index 74370cc3efe..a4e6aed86a0 100644 --- a/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json +++ b/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json @@ -13,7 +13,7 @@ "fieldname": "problem", "fieldtype": "Long Text", "in_list_view": 1, - "label": "Problem" + "label": "Review" }, { "fieldname": "sb_00", diff --git a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json index f5c0fbc2523..0a67fa505ee 100644 --- a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json +++ b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json @@ -18,7 +18,7 @@ "fieldname": "procedure", "fieldtype": "Link", "in_list_view": 1, - "label": "Procedure", + "label": "Child Procedure", "options": "Quality Procedure" } ], diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 7388ea0e729..630d5fae2a6 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -10,3 +10,21 @@ def check_deletion_permission(doc, method): region = get_region(doc.company) if region in ["Nepal", "France"] and doc.docstatus != 0: frappe.throw(_("Deletion is not permitted for country {0}".format(region))) + +def create_transaction_log(doc, method): + """ + Appends the transaction to a chain of hashed logs for legal resons. + Called on submit of Sales Invoice and Payment Entry. + """ + region = get_region() + if region not in ["France", "Germany"]: + return + + data = str(doc.as_dict()) + + frappe.get_doc({ + "doctype": "Transaction Log", + "reference_doctype": doc.doctype, + "document_name": doc.name, + "data": data + }).insert(ignore_permissions=True) diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js index e7cc91952d8..7ff4de48639 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js @@ -3,6 +3,26 @@ frappe.ui.form.on('GST HSN Code', { refresh: function(frm) { - + if(! frm.doc.__islocal && frm.doc.taxes.length){ + frm.add_custom_button(__('Update Taxes for Items'), function(){ + frappe.confirm( + 'Are you sure? It will overwrite taxes for all items with HSN Code '+frm.doc.name+'.', + function(){ + frappe.call({ + args:{ + taxes: frm.doc.taxes, + hsn_code: frm.doc.name + }, + method: 'erpnext.regional.doctype.gst_hsn_code.gst_hsn_code.update_taxes_in_item_master', + callback: function(r) { + if(r.message){ + frappe.show_alert(__('Item taxes updated')); + } + } + }); + } + ); + }); + } } -}); +}); \ No newline at end of file diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json index 2a2145c7f71..06dab3726d7 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json @@ -1,104 +1,46 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:hsn_code", - "beta": 0, - "creation": "2017-06-21 10:48:56.422086", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "autoname": "field:hsn_code", + "creation": "2017-06-21 10:48:56.422086", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "hsn_code", + "description", + "taxes" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hsn_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "HSN Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "hsn_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "HSN Code", + "reqd": 1, + "unique": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description" + }, + { + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Taxes", + "options": "Item Tax" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-09-29 14:38:52.220743", - "modified_by": "Administrator", - "module": "Regional", - "name": "GST HSN Code", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "hsn_code, description", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "hsn_code", - "track_changes": 1, - "track_seen": 0 + ], + "modified": "2019-11-01 11:18:59.556931", + "modified_by": "Administrator", + "module": "Regional", + "name": "GST HSN Code", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "search_fields": "hsn_code, description", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "hsn_code", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py index 9637c2e502a..86cd4d1545d 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py @@ -8,3 +8,26 @@ from frappe.model.document import Document class GSTHSNCode(Document): pass + +@frappe.whitelist() +def update_taxes_in_item_master(taxes, hsn_code): + items = frappe.get_list("Item", filters={ + 'gst_hsn_code': hsn_code + }) + + taxes = frappe.parse_json(taxes) + frappe.enqueue(update_item_document, items=items, taxes=taxes) + return 1 + +def update_item_document(items, taxes): + for item in items: + item_to_be_updated=frappe.get_doc("Item", item.name) + item_to_be_updated.taxes = [] + for tax in taxes: + tax = frappe._dict(tax) + item_to_be_updated.append("taxes", { + 'item_tax_template': tax.item_tax_template, + 'tax_category': tax.tax_category, + 'valid_from': tax.valid_from + }) + item_to_be_updated.save() \ No newline at end of file diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index fef73d9a0a8..fa6fb706e9b 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -64,7 +64,8 @@ class TestGSTR3BReport(unittest.TestCase): self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18), self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100), self.assertEqual(output["inward_sup"]["isup_details"][0]["inter"], 250) - self.assertEqual(output["itc_elg"]["itc_avl"][4]["iamt"], 45) + self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50) + self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50) def make_sales_invoice(): si = create_sales_invoice(company="_Test Company GST", @@ -158,10 +159,18 @@ def create_purchase_invoices(): pi.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "CGST - _GST", "cost_center": "Main - _GST", - "description": "IGST @ 18.0", - "rate": 18 + "description": "CGST @ 9.0", + "rate": 9 + }) + + pi.append("taxes", { + "charge_type": "On Net Total", + "account_head": "SGST - _GST", + "cost_center": "Main - _GST", + "description": "SGST @ 9.0", + "rate": 9 }) pi.submit() diff --git a/erpnext/regional/doctype/import_supplier_invoice/__init__.py b/erpnext/regional/doctype/import_supplier_invoice/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js new file mode 100644 index 00000000000..c2d6edfc773 --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js @@ -0,0 +1,46 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.ui.form.on('Import Supplier Invoice', { + onload: function(frm) { + frappe.realtime.on("import_invoice_update", function (data) { + frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message); + if (data.count == data.total) { + window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title); + } + }); + }, + setup: function(frm) { + frm.set_query("tax_account", function(doc) { + return { + filters: { + account_type: 'Tax', + company: doc.company + } + }; + }); + + frm.set_query("default_buying_price_list", function(doc) { + return { + filters: { + currency: frappe.get_doc(":Company", doc.company).default_currency + } + }; + }); + }, + + refresh: function(frm) { + frm.trigger("toggle_read_only_fields"); + }, + + toggle_read_only_fields: function(frm) { + if (in_list(["File Import Completed", "Processing File Data"], frm.doc.status)) { + cur_frm.set_read_only(); + cur_frm.refresh_fields(); + frm.set_df_property("import_invoices", "hidden", 1); + } else { + frm.set_df_property("import_invoices", "hidden", 0); + } + } + +}); \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json new file mode 100644 index 00000000000..59e955c23f4 --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json @@ -0,0 +1,105 @@ +{ + "actions": [], + "creation": "2019-10-15 12:33:21.845329", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "invoice_series", + "company", + "item_code", + "column_break_5", + "supplier_group", + "tax_account", + "default_buying_price_list", + "upload_xml_invoices_section", + "zip_file", + "import_invoices", + "status" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1 + }, + { + "fieldname": "supplier_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Supplier Group", + "options": "Supplier Group", + "reqd": 1 + }, + { + "fieldname": "tax_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Tax Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "zip_file", + "fieldtype": "Attach", + "label": "Zip File" + }, + { + "description": "Click on Import Invoices button once the zip file has been attached to the document. Any errors related to processing will be shown in the Error Log.", + "fieldname": "import_invoices", + "fieldtype": "Button", + "label": "Import Invoices", + "options": "process_file_data" + }, + { + "fieldname": "status", + "fieldtype": "Data", + "label": "Status", + "read_only": 1 + }, + { + "fieldname": "invoice_series", + "fieldtype": "Select", + "label": "Invoice Series", + "options": "ACC-PINV-.YYYY.-", + "reqd": 1 + }, + { + "fieldname": "default_buying_price_list", + "fieldtype": "Link", + "label": "Default Buying Price List", + "options": "Price List", + "reqd": 1 + }, + { + "fieldname": "upload_xml_invoices_section", + "fieldtype": "Section Break", + "label": "Upload XML Invoices" + } + ], + "links": [], + "modified": "2019-12-10 16:37:26.793398", + "modified_by": "Administrator", + "module": "Regional", + "name": "Import Supplier Invoice", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py new file mode 100644 index 00000000000..72fe17fb379 --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -0,0 +1,402 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +from decimal import Decimal +import json +import re +import traceback +import zipfile +import frappe, erpnext +from frappe import _ +from frappe.model.document import Document +from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.utils.data import format_datetime +from bs4 import BeautifulSoup as bs +from frappe.utils import cint, flt, today, nowdate, add_days, get_files_path, get_datetime_str +import dateutil +from frappe.utils.file_manager import save_file + +class ImportSupplierInvoice(Document): + def validate(self): + if not frappe.db.get_value("Stock Settings", fieldname="stock_uom"): + frappe.throw(_("Please set default UOM in Stock Settings")) + + def autoname(self): + if not self.name: + self.name = "Import Invoice on " + format_datetime(self.creation) + + def import_xml_data(self): + import_file = frappe.get_doc("File", {"file_url": self.zip_file}) + self.publish("File Import", _("Processing XML Files"), 1, 3) + + self.file_count = 0 + self.purchase_invoices_count = 0 + self.default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom") + + with zipfile.ZipFile(get_full_path(self.zip_file)) as zf: + for file_name in zf.namelist(): + content = get_file_content(file_name, zf) + file_content = bs(content, "xml") + self.prepare_data_for_import(file_content, file_name, content) + + if self.purchase_invoices_count == self.file_count: + self.status = "File Import Completed" + self.publish("File Import", _("XML Files Processed"), 2, 3) + else: + self.status = "Partially Completed - Check Error Log" + self.publish("File Import", _("XML Files Processed"), 2, 3) + + self.save() + self.publish("File Import", _("XML Files Processed"), 3, 3) + + def prepare_data_for_import(self, file_content, file_name, encoded_content): + for line in file_content.find_all("DatiGeneraliDocumento"): + invoices_args = { + "company": self.company, + "naming_series": self.invoice_series, + "document_type": line.TipoDocumento.text, + "bill_date": get_datetime_str(line.Data.text), + "invoice_no": line.Numero.text, + "total_discount": 0, + "items": [], + "buying_price_list": self.default_buying_price_list + } + + if not invoices_args.get("invoice_no", ''): return + + supp_dict = get_supplier_details(file_content) + invoices_args["destination_code"] = get_destination_code_from_file(file_content) + self.prepare_items_for_invoice(file_content, invoices_args) + invoices_args["taxes"] = get_taxes_from_file(file_content, self.tax_account) + invoices_args["terms"] = get_payment_terms_from_file(file_content) + + supplier_name = create_supplier(self.supplier_group, supp_dict) + address = create_address(supplier_name, supp_dict) + pi_name = create_purchase_invoice(supplier_name, file_name, invoices_args, self.name) + + self.file_count += 1 + if pi_name: + self.purchase_invoices_count += 1 + file_save = save_file(file_name, encoded_content, "Purchase Invoice", + pi_name, folder=None, decode=False, is_private=0, df=None) + + def prepare_items_for_invoice(self, file_content, invoices_args): + qty = 1 + rate, tax_rate = [0 ,0] + uom = self.default_uom + + #read file for item information + for line in file_content.find_all("DettaglioLinee"): + if line.find("PrezzoUnitario") and line.find("PrezzoTotale"): + rate = flt(line.PrezzoUnitario.text) or 0 + line_total = flt(line.PrezzoTotale.text) or 0 + + if rate and flt(line_total) / rate != 1.0 and line.find("Quantita"): + qty = flt(line.Quantita.text) or 0 + if line.find("UnitaMisura"): + uom = create_uom(line.UnitaMisura.text) + + if (rate < 0 and line_total < 0): + qty *= -1 + invoices_args["return_invoice"] = 1 + + if line.find("AliquotaIVA"): + tax_rate = flt(line.AliquotaIVA.text) + + line_str = re.sub('[^A-Za-z0-9]+', '-', line.Descrizione.text) + item_name = line_str[0:140] + + invoices_args['items'].append({ + "item_code": self.item_code, + "item_name": item_name, + "description": line_str, + "qty": qty, + "uom": uom, + "rate": abs(rate), + "conversion_factor": 1.0, + "tax_rate": tax_rate + }) + + for disc_line in line.find_all("ScontoMaggiorazione"): + if disc_line.find("Percentuale"): + invoices_args["total_discount"] += flt((flt(disc_line.Percentuale.text) / 100) * (rate * qty)) + + def process_file_data(self): + self.status = "Processing File Data" + self.save() + frappe.enqueue_doc(self.doctype, self.name, "import_xml_data", queue="long", timeout=3600) + + def publish(self, title, message, count, total): + frappe.publish_realtime("import_invoice_update", {"title": title, "message": message, "count": count, "total": total}) + +def get_file_content(file_name, zip_file_object): + content = '' + encoded_content = zip_file_object.read(file_name) + + try: + content = encoded_content.decode("utf-8-sig") + except UnicodeDecodeError: + try: + content = encoded_content.decode("utf-16") + except UnicodeDecodeError as e: + frappe.log_error(message=e, title="UTF-16 encoding error for File Name: " + file_name) + + return content + +def get_supplier_details(file_content): + supplier_info = {} + for line in file_content.find_all("CedentePrestatore"): + supplier_info['tax_id'] = line.DatiAnagrafici.IdPaese.text + line.DatiAnagrafici.IdCodice.text + if line.find("CodiceFiscale"): + supplier_info['fiscal_code'] = line.DatiAnagrafici.CodiceFiscale.text + + if line.find("RegimeFiscale"): + supplier_info['fiscal_regime'] = line.DatiAnagrafici.RegimeFiscale.text + + if line.find("Denominazione"): + supplier_info['supplier'] = line.DatiAnagrafici.Anagrafica.Denominazione.text + + if line.find("Nome"): + supplier_info['supplier'] = (line.DatiAnagrafici.Anagrafica.Nome.text + + " " + line.DatiAnagrafici.Anagrafica.Cognome.text) + + supplier_info['address_line1'] = line.Sede.Indirizzo.text + supplier_info['city'] = line.Sede.Comune.text + if line.find("Provincia"): + supplier_info['province'] = line.Sede.Provincia.text + + supplier_info['pin_code'] = line.Sede.CAP.text + supplier_info['country'] = get_country(line.Sede.Nazione.text) + + return supplier_info + +def get_taxes_from_file(file_content, tax_account): + taxes = [] + #read file for taxes information + for line in file_content.find_all("DatiRiepilogo"): + if line.find("AliquotaIVA"): + if line.find("EsigibilitaIVA"): + descr = line.EsigibilitaIVA.text + else: + descr = "None" + taxes.append({ + "charge_type": "Actual", + "account_head": tax_account, + "tax_rate": flt(line.AliquotaIVA.text) or 0, + "description": descr, + "tax_amount": flt(line.Imposta.text) if len(line.find("Imposta"))!=0 else 0 + }) + + return taxes + +def get_payment_terms_from_file(file_content): + terms = [] + #Get mode of payment dict from setup + mop_options = frappe.get_meta('Mode of Payment').fields[4].options + mop_str = re.sub('\n', ',', mop_options) + mop_dict = dict(item.split("-") for item in mop_str.split(",")) + #read file for payment information + for line in file_content.find_all("DettaglioPagamento"): + mop_code = line.ModalitaPagamento.text + '-' + mop_dict.get(line.ModalitaPagamento.text) + if line.find("DataScadenzaPagamento"): + due_date = dateutil.parser.parse(line.DataScadenzaPagamento.text).strftime("%Y-%m-%d") + else: + due_date = today() + terms.append({ + "mode_of_payment_code": mop_code, + "bank_account_iban": line.IBAN.text if line.find("IBAN") else "", + "due_date": due_date, + "payment_amount": line.ImportoPagamento.text + }) + + return terms + +def get_destination_code_from_file(file_content): + destination_code = '' + for line in file_content.find_all("DatiTrasmissione"): + destination_code = line.CodiceDestinatario.text + + return destination_code + +def create_supplier(supplier_group, args): + args = frappe._dict(args) + + existing_supplier_name = frappe.db.get_value("Supplier", + filters={"tax_id": args.tax_id}, fieldname="name") + if existing_supplier_name: + pass + else: + existing_supplier_name = frappe.db.get_value("Supplier", + filters={"name": args.supplier}, fieldname="name") + + if existing_supplier_name: + filters = [ + ["Dynamic Link", "link_doctype", "=", "Supplier"], + ["Dynamic Link", "link_name", "=", args.existing_supplier_name], + ["Dynamic Link", "parenttype", "=", "Contact"] + ] + + if not frappe.get_list("Contact", filters): + new_contact = frappe.new_doc("Contact") + new_contact.first_name = args.supplier + new_contact.append('links', { + "link_doctype": "Supplier", + "link_name": existing_supplier_name + }) + new_contact.insert(ignore_mandatory=True) + + return existing_supplier_name + else: + + new_supplier = frappe.new_doc("Supplier") + new_supplier.supplier_name = args.supplier + new_supplier.supplier_group = supplier_group + new_supplier.tax_id = args.tax_id + new_supplier.fiscal_code = args.fiscal_code + new_supplier.fiscal_regime = args.fiscal_regime + new_supplier.save() + + new_contact = frappe.new_doc("Contact") + new_contact.first_name = args.supplier + new_contact.append('links', { + "link_doctype": "Supplier", + "link_name": new_supplier.name + }) + + new_contact.insert(ignore_mandatory=True) + + return new_supplier.name + +def create_address(supplier_name, args): + args = frappe._dict(args) + + filters = [ + ["Dynamic Link", "link_doctype", "=", "Supplier"], + ["Dynamic Link", "link_name", "=", supplier_name], + ["Dynamic Link", "parenttype", "=", "Address"] + ] + + existing_address = frappe.get_list("Address", filters) + + if args.address_line1: + new_address_doc = frappe.new_doc("Address") + new_address_doc.address_line1 = args.address_line1 + + if args.city: + new_address_doc.city = args.city + else: + new_address_doc.city = "Not Provided" + + for field in ["province", "pincode", "country"]: + if args.get(field): + new_address_doc.set(field, args.get(field)) + + for address in existing_address: + address_doc = frappe.get_doc("Address", address["name"]) + if (address_doc.address_line1 == new_address_doc.address_line1 and + address_doc.pincode == new_address_doc.pincode): + return address + + new_address_doc.append("links", { + "link_doctype": "Supplier", + "link_name": supplier_name + }) + new_address_doc.address_type = "Billing" + new_address_doc.insert(ignore_mandatory=True) + return new_address_doc.name + else: + return None + +def create_purchase_invoice(supplier_name, file_name, args, name): + args = frappe._dict(args) + pi = frappe.get_doc({ + "doctype": "Purchase Invoice", + "company": args.company, + "currency": erpnext.get_company_currency(args.company), + "naming_series": args.naming_series, + "supplier": supplier_name, + "is_return": args.is_return, + "posting_date": today(), + "bill_no": args.bill_no, + "buying_price_list": args.buying_price_list, + "bill_date": args.bill_date, + "destination_code": args.destination_code, + "document_type": args.document_type, + "disable_rounded_total": 1, + "items": args["items"], + "taxes": args["taxes"] + }) + + try: + pi.set_missing_values() + pi.insert(ignore_mandatory=True) + + #if discount exists in file, apply any discount on grand total + if args.total_discount > 0: + pi.apply_discount_on = "Grand Total" + pi.discount_amount = args.total_discount + pi.save() + #adjust payment amount to match with grand total calculated + calc_total = 0 + adj = 0 + for term in args.terms: + calc_total += flt(term["payment_amount"]) + if flt(calc_total - flt(pi.grand_total)) != 0: + adj = calc_total - flt(pi.grand_total) + pi.payment_schedule = [] + for term in args.terms: + pi.append('payment_schedule',{"mode_of_payment_code": term["mode_of_payment_code"], + "bank_account_iban": term["bank_account_iban"], + "due_date": term["due_date"], + "payment_amount": flt(term["payment_amount"]) - adj }) + adj = 0 + pi.imported_grand_total = calc_total + pi.save() + return pi.name + except Exception as e: + frappe.db.set_value("Import Supplier Invoice", name, "status", "Error") + frappe.log_error(message=e, + title="Create Purchase Invoice: " + args.get("bill_no") + "File Name: " + file_name) + return None + +def get_country(code): + existing_country_name = frappe.db.get_value("Country", + filters={"code": code}, fieldname="name") + if existing_country_name: + return existing_country_name + else: + frappe.throw(_("Country Code in File does not match with country code set up in the system")) + +def create_uom(uom): + existing_uom = frappe.db.get_value("UOM", + filters={"uom_name": uom}, fieldname="uom_name") + if existing_uom: + return existing_uom + else: + new_uom = frappe.new_doc("UOM") + new_uom.uom_name = uom + new_uom.save() + return new_uom.uom_name + +def get_full_path(file_name): + """Returns file path from given file name""" + file_path = file_name + + if "/" not in file_path: + file_path = "/files/" + file_path + + if file_path.startswith("/private/files/"): + file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1) + + elif file_path.startswith("/files/"): + file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/")) + + elif file_path.startswith("http"): + pass + + elif not self.file_url: + frappe.throw(_("There is some problem with the file url: {0}").format(file_path)) + + return file_path \ No newline at end of file diff --git a/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py new file mode 100644 index 00000000000..d1caf77fc2c --- /dev/null +++ b/erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestImportSupplierInvoice(unittest.TestCase): + pass diff --git a/erpnext/regional/france/utils.py b/erpnext/regional/france/utils.py index e4b72f65866..424615dbbc0 100644 --- a/erpnext/regional/france/utils.py +++ b/erpnext/regional/france/utils.py @@ -3,22 +3,6 @@ from __future__ import unicode_literals import frappe -from frappe import _ -from erpnext import get_region - -def create_transaction_log(doc, method): - region = get_region() - if region not in ["France"]: - return - else: - data = str(doc.as_dict()) - - frappe.get_doc({ - "doctype": "Transaction Log", - "reference_doctype": doc.doctype, - "document_name": doc.name, - "data": data - }).insert(ignore_permissions=True) # don't remove this function it is used in tests def test_method(): diff --git a/erpnext/regional/germany/address_template.html b/erpnext/regional/germany/address_template.html index 0df786713c9..7fa4c32612a 100644 --- a/erpnext/regional/germany/address_template.html +++ b/erpnext/regional/germany/address_template.html @@ -1,8 +1,8 @@ {{ address_line1 }}| \\nCedente/prestatore (fornitore)\\n | \\n\\nCessionario/committente (cliente)\\n | \\n\\n\\n
|---|---|
| \\n Identificstivo fiscale ai fini IVA: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"tax_id\\\")}} \\nCodice fiscale: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"fiscal_code\\\")}} \\nDenominazione: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"supplier_name\\\")}} \\nRegime fiscale: {{frappe.db.get_value(\\\"Supplier\\\", doc.supplier, \\\"fiscal_regime\\\")}} \\nIndrizo: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"address_line1\\\")}} \\nCommune: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"city\\\")}} Provincia: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"state_code\\\")}} \\nCap: {{(frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"pincode\\\")) or \\\" \\\"}} Nazione: {{frappe.db.get_value(\\\"Address\\\", doc.supplier_address, \\\"country\\\")}} \\n | \\n\\n Identificstivo fiscale ai fini IVA: {{frappe.db.get_value(\\\"Company\\\", doc.company, \\\"tax_id\\\")}} \\nCodice fiscale: {{frappe.db.get_value(\\\"Company\\\", doc.company, \\\"fiscal_code\\\")}} \\nDenominazione: {{doc.company}} \\nIndrizo: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"address_line1\\\")}} \\nCommune: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"city\\\")}} Provincia: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"state_code\\\")}} \\nCap: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"pincode\\\")}} Nazione: {{frappe.db.get_value(\\\"Address\\\", doc.shipping_address, \\\"country\\\")}} \\n | \\n\\n
| \\nTipologla\\n | \\n\\nArt. 73\\n | \\n\\nNumero documento\\n | \\n\\nData documento\\n | \\n\\nCodice destinatario\\n | \\n\\n\\n
|---|---|---|---|---|
| \\n{{doc.document_type or \\\" \\\"}}\\n | \\n\\n{{\\\" \\\"}}\\n | \\n\\n{{doc.bill_no or \\\" \\\"}}\\n | \\n\\n{{doc.get_formatted(\\\"bill_date\\\") or \\\" \\\"}}\\n | \\n\\n{{doc.destination_code or \\\" \\\"}}\\n | \\n
| \\nDescrizione\\n | \\n\\nQuantita\\n | \\n\\nPrezzo unitario\\n | \\n\\nUM\\n | \\n\\n%IVA\\n | \\n\\nPrezzo totale\\n | \\n\\n\\n\\n{%- for row in doc.items -%}\\n
|---|---|---|---|---|---|
| \\n{{row.description or \\\" \\\"}}\\n | \\n\\n{{row.get_formatted(\\\"qty\\\", doc)}}\\n | \\n\\n{{row.get_formatted(\\\"rate\\\", doc)}}\\n | \\n\\n{{row.get_formatted(\\\"uom\\\", doc)}}\\n | \\n\\n{{row.get_formatted(\\\"tax_rate\\\", doc)}}\\n | \\n\\n{{row.get_formatted(\\\"amount\\\", doc)}}\\n | \\n{%- endfor -%}\\n\\n
| \\nesigibilita immediata / riferimenti normativi\\n | \\n\\n%IVA\\n | \\n\\nSpese accessorie\\n | \\n\\nArr.\\n | \\n\\nTotale imponibile\\n | \\n\\nTotale Imposta\\n | \\n\\n\\n\\n{%- for row in doc.taxes -%}\\n
|---|---|---|---|---|---|
| \\n{% if 'None' in row.description %}\\n {{ \\\" \\\" }}\\n{% else %}\\n{{row.description}}\\n{% endif %}\\n | \\n\\n{{row.get_formatted(\\\"tax_rate\\\", doc)}}\\n | \\n\\n{{\\\"0,00\\\"}}\\n | \\n\\n{{\\\" \\\"}}\\n | \\n\\n{{doc.get_formatted(\\\"base_net_total\\\")}}\\n | \\n\\n{{row.get_formatted(\\\"tax_amount\\\", doc)}}\\n | \\n{%- endfor -%}\\n\\n
| \\nImporto bolio\\n | \\n\\nSconto/Magglorazione\\n | \\n\\nArr.\\n | \\n\\nTotale documento\\n | \\n\\n\\n\\n
|---|---|---|---|
| \\n{{\\\" \\\"}}\\n | \\n\\n{{\\\" \\\"}}\\n | \\n\\n{{\\\" \\\"}}\\n | \\n\\n{{doc.get_formatted(\\\"base_grand_total\\\")}}\\n | \\n\\n
| \\nModalita pagamento\\n | \\n\\nIBAN\\n | \\n\\nInstituto\\n | \\n\\nData scadenza\\n | \\n\\nImporto\\n | \\n\\n\\n\\n{%- for row in doc.payment_schedule -%}\\n
|---|---|---|---|---|
| \\n{{row.get_formatted(\\\"mode_of_payment_code\\\",doc)}}\\n | \\n\\n{{row.get_formatted(\\\"bank_account_iban\\\",doc)}}\\n | \\n\\n{{\\\" \\\"}}\\n | \\n\\n{{row.get_formatted(\\\"due_date\\\",doc)}}\\n | \\n\\n{{row.get_formatted(\\\"payment_amount\\\",doc)}}\\n | \\n{%- endfor -%}\\n\\n
Let's continue where you left from!
", + "slide_fields": [], + "slide_module": "Setup", + "slide_order": 0, + "slide_title": "Welcome back to ERPNext!", + "slide_type": "Continue" +} \ No newline at end of file diff --git a/erpnext/setup/onboarding_slide/welcome_to_erpnext!/welcome_to_erpnext!.json b/erpnext/setup/onboarding_slide/welcome_to_erpnext!/welcome_to_erpnext!.json new file mode 100644 index 00000000000..37eb67b1d7e --- /dev/null +++ b/erpnext/setup/onboarding_slide/welcome_to_erpnext!/welcome_to_erpnext!.json @@ -0,0 +1,23 @@ +{ + "add_more_button": 0, + "app": "ERPNext", + "creation": "2019-11-26 17:01:26.671859", + "docstatus": 0, + "doctype": "Onboarding Slide", + "domains": [], + "help_links": [], + "idx": 0, + "image_src": "", + "is_completed": 0, + "max_count": 0, + "modified": "2019-12-22 21:26:28.414597", + "modified_by": "Administrator", + "name": "Welcome to ERPNext!", + "owner": "Administrator", + "slide_desc": "
- + ${__("Notes")}:
{0}").format(', '.join(form_links))) + message = _("The following serial numbers were created: {0}").format(get_items_html(form_links, item_code)) + frappe.msgprint(message, multiple_title) + +def get_items_html(serial_nos, item_code): + body = ', '.join(serial_nos) + return ''' + {0}: {1} Serial Numbers ++{2} {{_("Dear")}} {{ full_name }}{% if last_name %} {{ last_name}}{% endif %}, +{{_("A new appointment has been created for you with {0}").format(site_url)}}. +{{_("Click on the link below to verify your email and confirm the appointment")}}. + + + ++ {{_("You can also copy-paste this link in your browser")}} {{ link }} diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html index fe53f34dba5..f7f35483208 100644 --- a/erpnext/templates/includes/cart/cart_address.html +++ b/erpnext/templates/includes/cart/cart_address.html @@ -64,16 +64,6 @@ frappe.ready(() => { fieldtype: 'Data', reqd: 1 }, - { - label: __('Address Type'), - fieldname: 'address_type', - fieldtype: 'Select', - options: [ - 'Billing', - 'Shipping' - ], - reqd: 1 - }, { label: __('Address Line 1'), fieldname: 'address_line1', @@ -96,16 +86,37 @@ frappe.ready(() => { fieldname: 'state', fieldtype: 'Data' }, + { + label: __('Country'), + fieldname: 'country', + fieldtype: 'Link', + options: 'Country', + reqd: 1 + }, + { + fieldname: "column_break0", + fieldtype: "Column Break", + width: "50%" + }, + { + label: __('Address Type'), + fieldname: 'address_type', + fieldtype: 'Select', + options: [ + 'Billing', + 'Shipping' + ], + reqd: 1 + }, { label: __('Pin Code'), fieldname: 'pincode', fieldtype: 'Data' }, { - label: __('Country'), - fieldname: 'country', - fieldtype: 'Link', - reqd: 1 + fieldname: "phone", + fieldtype: "Data", + label: "Phone" }, ], primary_action_label: __('Save'), diff --git a/erpnext/templates/includes/timesheet/timesheet_row.html b/erpnext/templates/includes/timesheet/timesheet_row.html index e9cfcda8125..4852f59b5d2 100644 --- a/erpnext/templates/includes/timesheet/timesheet_row.html +++ b/erpnext/templates/includes/timesheet/timesheet_row.html @@ -1,13 +1,14 @@ -
diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py
index fc850d57d76..ce0f47d685f 100644
--- a/erpnext/tests/test_woocommerce.py
+++ b/erpnext/tests/test_woocommerce.py
@@ -18,6 +18,7 @@ class TestWoocommerce(unittest.TestCase):
woo_settings.api_consumer_key = "ck_fd43ff5756a6abafd95fadb6677100ce95a758a1"
woo_settings.api_consumer_secret = "cs_94360a1ad7bef7fa420a40cf284f7b3e0788454e"
woo_settings.enable_sync = 1
+ woo_settings.company = "Woocommerce"
woo_settings.tax_account = "Sales Expenses - W"
woo_settings.f_n_f_account = "Expenses - W"
woo_settings.creation_user = "Administrator"
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 2e25a127d8e..954f6db5b4e 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -304,7 +304,7 @@ apps/erpnext/erpnext/selling/report/sales_partner_target_variance_based_on_item_
DocType: BOM,Total Cost,Gesamtkosten
apps/erpnext/erpnext/hr/doctype/leave_allocation/leave_allocation.js,Allocation Expired!,Zuteilung abgelaufen!
DocType: Soil Analysis,Ca/K,Ca / K
-DocType: Leave Type,Maximum Carry Forwarded Leaves,Maximale Anzahl weitergeleiteter Blätter
+DocType: Leave Type,Maximum Carry Forwarded Leaves,Obergrenze für übertragbaren Urlaub erreicht
DocType: Salary Slip,Employee Loan,MItarbeiterdarlehen
DocType: Additional Salary,HR-ADS-.YY.-.MM.-,HR-ADS-.YY .-. MM.-
DocType: Fee Schedule,Send Payment Request Email,Zahlungaufforderung per E-Mail versenden
@@ -893,7 +893,7 @@ DocType: Supplier Scorecard Standing,Notify Other,Andere benachrichtigen
DocType: Vital Signs,Blood Pressure (systolic),Blutdruck (systolisch)
apps/erpnext/erpnext/controllers/buying_controller.py,{0} {1} is {2},{0} {1} ist {2}
DocType: Item Price,Valid Upto,Gültig bis
-DocType: Leave Type,Expire Carry Forwarded Leaves (Days),Verfallsdatum für weitergeleitete Blätter (Tage)
+DocType: Leave Type,Expire Carry Forwarded Leaves (Days),Verfallsdatum für übertragenen Urlaub (Tage)
DocType: Training Event,Workshop,Werkstatt
DocType: Supplier Scorecard Scoring Standing,Warn Purchase Orders,Warnung Bestellungen
apps/erpnext/erpnext/utilities/user_progress.py,List a few of your customers. They could be organizations or individuals.,Bitte ein paar Kunden angeben. Dies können Firmen oder Einzelpersonen sein.
@@ -1103,7 +1103,7 @@ apps/erpnext/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py,
DocType: Payroll Entry,Select Payment Account to make Bank Entry,Wählen Sie ein Zahlungskonto für die Buchung
apps/erpnext/erpnext/config/accounting.py,Opening and Closing,Öffnen und Schließen
DocType: Hotel Settings,Default Invoice Naming Series,Standard-Rechnungsnummernkreis
-apps/erpnext/erpnext/utilities/activation.py,"Create Employee records to manage leaves, expense claims and payroll","Erstellen Sie Mitarbeiterdaten Blätter, Spesenabrechnung und Gehaltsabrechnung zu verwalten"
+apps/erpnext/erpnext/utilities/activation.py,"Create Employee records to manage leaves, expense claims and payroll","Erstellen Sie Mitarbeiterdaten für Urlaubs, Spesenabrechnung und Gehaltsabrechnung zu verwalten"
apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js,An error occurred during the update process,Während des Aktualisierungsprozesses ist ein Fehler aufgetreten
DocType: Restaurant Reservation,Restaurant Reservation,Restaurant Reservierung
apps/erpnext/erpnext/public/js/hub/Sidebar.vue,Your Items,Ihre Artikel
@@ -1329,7 +1329,7 @@ apps/erpnext/erpnext/hr/doctype/job_offer/job_offer.js,Create Employee,Mitarbeit
apps/erpnext/erpnext/utilities/transaction_base.py,Invalid Posting Time,Ungültige Buchungszeit
DocType: Salary Component,Condition and Formula,Zustand und Formel
DocType: Lead,Campaign Name,Kampagnenname
-apps/erpnext/erpnext/setup/default_energy_point_rules.py,On Task Completion,Bei Abschluss der Aufgabe
+apps/erpnext/erpnext/setup/default_energy_point_rules.py,On Task Completion,Bei Abschluss des Vorgangs
apps/erpnext/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py,There is no leave period in between {0} and {1},Es gibt keinen Urlaub zwischen {0} und {1}
DocType: Fee Validity,Healthcare Practitioner,praktischer Arzt
DocType: Hotel Room,Capacity,Kapazität
@@ -1353,7 +1353,7 @@ DocType: Payment Entry,Received Amount (Company Currency),Erhaltene Menge (Gesel
apps/erpnext/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py,Payment Cancelled. Please check your GoCardless Account for more details,Zahlung abgebrochen. Bitte überprüfen Sie Ihr GoCardless Konto für weitere Details
DocType: Work Order,Skip Material Transfer to WIP Warehouse,Überspringen Sie die Materialübertragung in das WIP-Lager
DocType: Contract,N/A,nicht verfügbar
-DocType: Task Type,Task Type,Aufgabentyp
+DocType: Task Type,Task Type,Vorgangstyp
DocType: Topic,Topic Content,Themeninhalt
DocType: Delivery Settings,Send with Attachment,Senden mit Anhang
DocType: Service Level,Priorities,Prioritäten
@@ -1548,7 +1548,7 @@ DocType: Share Transfer,To Shareholder,An den Aktionär
apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py,{0} against Bill {1} dated {2},{0} zu Rechnung {1} vom {2}
apps/erpnext/erpnext/regional/report/eway_bill/eway_bill.py,From State,Aus dem Staat
apps/erpnext/erpnext/utilities/user_progress.py,Setup Institution,Einrichtung Einrichtung
-apps/erpnext/erpnext/hr/doctype/leave_period/leave_period.py,Allocating leaves...,Blätter zuordnen...
+apps/erpnext/erpnext/hr/doctype/leave_period/leave_period.py,Allocating leaves...,Urlaub zuordnen...
DocType: Program Enrollment,Vehicle/Bus Number,Fahrzeug / Bus Nummer
apps/erpnext/erpnext/public/js/call_popup/call_popup.js,Create New Contact,Neuen Kontakt erstellen
apps/erpnext/erpnext/education/doctype/course/course.js,Course Schedule,Kurstermine
@@ -1806,7 +1806,7 @@ apps/erpnext/erpnext/setup/setup_wizard/data/industry_type.py,Chemical,Chemische
DocType: Salary Component Account,Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.,"Standard Bank / Geldkonto wird automatisch in Gehalts Journal Entry aktualisiert werden, wenn dieser Modus ausgewählt ist."
DocType: Quiz,Latest Attempt,Letzter Versuch
DocType: Quiz Result,Quiz Result,Quiz-Ergebnis
-apps/erpnext/erpnext/hr/doctype/leave_allocation/leave_allocation.py,Total leaves allocated is mandatory for Leave Type {0},Die Gesamtzahl der zugewiesenen Blätter ist für Abwesenheitsart {0} erforderlich.
+apps/erpnext/erpnext/hr/doctype/leave_allocation/leave_allocation.py,Total leaves allocated is mandatory for Leave Type {0},Die Gesamtzahl der zugewiesenen Urlaube ist für Abwesenheitsart {0} erforderlich.
apps/erpnext/erpnext/controllers/sales_and_purchase_return.py,Row # {0}: Rate cannot be greater than the rate used in {1} {2},"Row # {0}: Die Rate kann nicht größer sein als die Rate, die in {1} {2}"
apps/erpnext/erpnext/utilities/user_progress.py,Meter,Meter
DocType: Workstation,Electricity Cost,Stromkosten
@@ -1916,7 +1916,7 @@ apps/erpnext/erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.html,Go to
apps/erpnext/erpnext/templates/pages/order.js,Pay Remaining,Verbleibende Bezahlung
DocType: Purchase Invoice Item,Manufacturer,Hersteller
DocType: Landed Cost Item,Purchase Receipt Item,Kaufbeleg-Artikel
-DocType: Leave Allocation,Total Leaves Encashed,Insgesamt Blätter umkränzt
+DocType: Leave Allocation,Total Leaves Encashed,Summe ausbezahlter Urlaubstage
DocType: POS Profile,Sales Invoice Payment,Ausgangsrechnung-Zahlungen
DocType: Quality Inspection Template,Quality Inspection Template Name,Name der Qualitätsinspektionsvorlage
DocType: Project,First Email,Erste E-Mail
@@ -2449,7 +2449,7 @@ apps/erpnext/erpnext/public/js/utils/serial_no_batch_selector.js,Please select b
DocType: Asset,Depreciation Schedules,Abschreibungen Termine
apps/erpnext/erpnext/projects/doctype/timesheet/timesheet.js,Create Sales Invoice,Verkaufsrechnung erstellen
apps/erpnext/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html,Ineligible ITC,Nicht förderfähiges ITC
-DocType: Task,Dependent Tasks,Abhängige Aufgaben
+DocType: Task,Dependent Tasks,Abhängige Vorgänge
apps/erpnext/erpnext/regional/report/gstr_1/gstr_1.py,Following accounts might be selected in GST Settings:,In den GST-Einstellungen können folgende Konten ausgewählt werden:
apps/erpnext/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js,Quantity to Produce,Menge zu produzieren
apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,Application period cannot be outside leave allocation period,Beantragter Zeitraum kann nicht außerhalb der beantragten Urlaubszeit liegen
@@ -2540,7 +2540,7 @@ Used for Taxes and Charges",Die Tabelle Steuerdetails wird aus dem Artikelstamm
apps/erpnext/erpnext/hr/doctype/employee/employee.py,Employee cannot report to himself.,Mitarbeiter können nicht an sich selbst Bericht erstatten
apps/erpnext/erpnext/templates/pages/order.html,Rate:,Bewertung:
DocType: Bank Account,Change this date manually to setup the next synchronization start date,"Ändern Sie dieses Datum manuell, um das nächste Startdatum für die Synchronisierung festzulegen"
-DocType: Leave Type,Max Leaves Allowed,Max Blätter erlaubt
+DocType: Leave Type,Max Leaves Allowed,Höchstzahl erlaubter Urlaubstage
DocType: Account,"If the account is frozen, entries are allowed to restricted users.","Wenn das Konto gesperrt ist, sind einem eingeschränkten Benutzerkreis Buchungen erlaubt."
DocType: Email Digest,Bank Balance,Kontostand
apps/erpnext/erpnext/controllers/accounts_controller.py,Accounting Entry for {0}: {1} can only be made in currency: {2},Eine Buchung für {0}: {1} kann nur in der Währung: {2} vorgenommen werden
@@ -2846,7 +2846,7 @@ DocType: Loan,Applicant Type,Bewerbertyp
DocType: Purchase Invoice,03-Deficiency in services,03-Mangel an Dienstleistungen
DocType: Healthcare Settings,Default Medical Code Standard,Default Medical Code Standard
DocType: Purchase Invoice Item,HSN/SAC,HSN / SAC
-DocType: Project Template Task,Project Template Task,Projektvorlagenaufgabe
+DocType: Project Template Task,Project Template Task,Projektvorgangsvorlage
DocType: Accounts Settings,Over Billing Allowance (%),Mehr als Abrechnungsbetrag (%)
apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py,Purchase Receipt {0} is not submitted,Kaufbeleg {0} wurde nicht übertragen
DocType: Company,Default Payable Account,Standard-Verbindlichkeitenkonto
@@ -2990,7 +2990,7 @@ DocType: Student Group Creation Tool,Separate course based Group for every Batch
apps/erpnext/erpnext/config/support.py,Single unit of an Item.,Einzelnes Element eines Artikels
DocType: Fee Category,Fee Category,Gebührenkategorie
DocType: Agriculture Task,Next Business Day,Nächster Arbeitstag
-apps/erpnext/erpnext/hr/doctype/leave_application/leave_application_dashboard.html,Allocated Leaves,Zugewiesene Blätter
+apps/erpnext/erpnext/hr/doctype/leave_application/leave_application_dashboard.html,Allocated Leaves,Genehmigter Urlaub
DocType: Drug Prescription,Dosage by time interval,Dosierung nach Zeitintervall
apps/erpnext/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html,Total Taxable Value,Steuerpflichtiger Gesamtwert
DocType: Cash Flow Mapper,Section Header,Abschnitt Kopfzeile
@@ -3323,7 +3323,7 @@ DocType: Soil Texture,Silt,Schlick
,Qty to Order,Zu bestellende Menge
DocType: Period Closing Voucher,"The account head under Liability or Equity, in which Profit/Loss will be booked","Der Kontenkopf unter Eigen- oder Fremdkapital, in dem Gewinn / Verlust verbucht wird"
apps/erpnext/erpnext/accounts/doctype/budget/budget.py,Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4},Ein weiterer Budgeteintrag '{0}' existiert bereits für {1} '{2}' und für '{3}' für das Geschäftsjahr {4}
-apps/erpnext/erpnext/config/projects.py,Gantt chart of all tasks.,Gantt-Diagramm aller Aufgaben
+apps/erpnext/erpnext/config/projects.py,Gantt chart of all tasks.,Gantt-Diagramm aller Vorgänge
DocType: Opportunity,Mins to First Response,Minuten zum First Response
DocType: Pricing Rule,Margin Type,Margenart
apps/erpnext/erpnext/projects/doctype/project/project_dashboard.html,{0} hours,{0} Stunden
@@ -3774,10 +3774,10 @@ apps/erpnext/erpnext/public/js/templates/address_list.html,New Address,Neue Adre
DocType: Quality Inspection,Sample Size,Stichprobenumfang
apps/erpnext/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py,Please enter Receipt Document,Bitte geben Sie Eingangsbeleg
apps/erpnext/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py,All items have already been invoiced,Alle Artikel sind bereits abgerechnet
-apps/erpnext/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py,Leaves Taken,Blätter genommen
+apps/erpnext/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py,Leaves Taken,Urlaubstage genommen
apps/erpnext/erpnext/stock/doctype/packing_slip/packing_slip.py,Please specify a valid 'From Case No.',"Bitte eine eine gültige ""Von Fall Nr."" angeben"
apps/erpnext/erpnext/accounts/doctype/cost_center/cost_center_tree.js,Further cost centers can be made under Groups but entries can be made against non-Groups,"Weitere Kostenstellen können unter Gruppen angelegt werden, aber Buchungen können zu nicht-Gruppen erstellt werden"
-apps/erpnext/erpnext/hr/doctype/leave_allocation/leave_allocation.py,Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period,Die insgesamt zugewiesenen Blätter sind mehr Tage als die maximale Zuweisung von {0} Abwesenheitsart für den Mitarbeiter {1} in der Periode
+apps/erpnext/erpnext/hr/doctype/leave_allocation/leave_allocation.py,Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period,Die insgesamt zugewiesenen Urlaubstage sind mehr Tage als die maximale Zuweisung von {0} Abwesenheitsart für den Mitarbeiter {1} in der Periode
DocType: Branch,Branch,Betrieb
apps/erpnext/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html,"Other outward supplies(Nil rated,Exempted)","Sonstige Auslandslieferungen (ohne Rating, ausgenommen)"
DocType: Soil Analysis,Ca/(K+Ca+Mg),Ca / (K + Ca + Mg)
@@ -3961,7 +3961,7 @@ apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Maintenance Sche
apps/erpnext/erpnext/education/doctype/student/student_dashboard.py,Student LMS Activity,Student LMS Aktivität
DocType: POS Profile,Applicable for Users,Anwendbar für Benutzer
DocType: Supplier Quotation,PUR-SQTN-.YYYY.-,PUR-SQTN-.JJJJ.-
-apps/erpnext/erpnext/projects/doctype/project/project.js,Set Project and all Tasks to status {0}?,Projekt und alle Aufgaben auf Status {0} setzen?
+apps/erpnext/erpnext/projects/doctype/project/project.js,Set Project and all Tasks to status {0}?,Projekt und alle Vorgänge auf Status {0} setzen?
DocType: Purchase Invoice,Set Advances and Allocate (FIFO),Vorschüsse setzen und zuordnen (FIFO)
apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.py,No Work Orders created,Keine Arbeitsaufträge erstellt
apps/erpnext/erpnext/hr/doctype/salary_slip/salary_slip.py,Salary Slip of employee {0} already created for this period,Gehaltsabrechnung der Mitarbeiter {0} für diesen Zeitraum bereits erstellt
@@ -4097,7 +4097,7 @@ DocType: Work Order,Actual End Date,Tatsächliches Enddatum
DocType: Cash Flow Mapping,Is Finance Cost Adjustment,Ist Finanzkostenanpassung
DocType: BOM,Operating Cost (Company Currency),Betriebskosten (Gesellschaft Währung)
DocType: Authorization Rule,Applicable To (Role),Anwenden auf (Rolle)
-apps/erpnext/erpnext/hr/doctype/leave_application/leave_application_dashboard.html,Pending Leaves,Ausstehende Blätter
+apps/erpnext/erpnext/hr/doctype/leave_application/leave_application_dashboard.html,Pending Leaves,Schwebende Urlaubstage
DocType: BOM Update Tool,Replace BOM,Erstelle Stückliste
apps/erpnext/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py,Code {0} already exist,Code {0} existiert bereits
DocType: Patient Encounter,Procedures,Verfahren
@@ -4342,7 +4342,7 @@ apps/erpnext/erpnext/accounts/report/asset_depreciations_and_balances/asset_depr
DocType: Sales Invoice,Is Return (Credit Note),ist Rücklieferung (Gutschrift)
apps/erpnext/erpnext/manufacturing/doctype/job_card/job_card.js,Start Job,Job starten
apps/erpnext/erpnext/assets/doctype/asset_movement/asset_movement.py,Serial no is required for the asset {0},Für Vermögenswert {0} ist eine Seriennr. Erforderlich.
-DocType: Leave Control Panel,Allocate Leaves,Blätter zuweisen
+DocType: Leave Control Panel,Allocate Leaves,Urlaubstage zuweisen
apps/erpnext/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py,Disabled template must not be default template,Deaktivierte Vorlage darf nicht Standardvorlage sein
DocType: Pricing Rule,Price or Product Discount,Preis- oder Produktrabatt
apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.py,For row {0}: Enter planned qty,Für Zeile {0}: Geben Sie die geplante Menge ein
@@ -4418,7 +4418,7 @@ DocType: Normal Test Items,Result Value,Ergebnis Wert
DocType: Hotel Room,Hotels,Hotels
apps/erpnext/erpnext/accounts/doctype/cost_center/cost_center_tree.js,New Cost Center Name,Neuer Kostenstellenname
DocType: Leave Control Panel,Leave Control Panel,Urlaubsverwaltung
-DocType: Project,Task Completion,Aufgabenerledigung
+DocType: Project,Task Completion,Vorgangserfüllung
apps/erpnext/erpnext/templates/generators/item/item_add_to_cart.html,Not in Stock,Nicht lagernd
DocType: Volunteer,Volunteer Skills,Freiwillige Fähigkeiten
DocType: Additional Salary,HR User,Nutzer Personalabteilung
@@ -4440,7 +4440,7 @@ DocType: Loan,Loan Application,Kreditantrag
DocType: Crop,Scientific Name,Wissenschaftlicher Name
DocType: Healthcare Service Unit,Service Unit Type,Serviceeinheitstyp
DocType: Bank Account,Branch Code,Bankleitzahl / BIC
-apps/erpnext/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py,Total Leaves,insgesamt Blätter
+apps/erpnext/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py,Total Leaves,summe der Urlaubstage
DocType: Customer,"Reselect, if the chosen contact is edited after save","Wählen Sie erneut, wenn der ausgewählte Kontakt nach dem Speichern bearbeitet wird"
DocType: Quality Procedure,Parent Procedure,Übergeordnetes Verfahren
DocType: Patient Encounter,In print,in Druckbuchstaben
@@ -5197,7 +5197,7 @@ DocType: Work Order,Material Transferred for Manufacturing,Material zur Herstell
apps/erpnext/erpnext/accounts/report/general_ledger/general_ledger.py,Account {0} does not exists,Konto {0} existiert nicht
apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.js,Select Loyalty Program,Wählen Sie Treueprogramm
DocType: Project,Project Type,Projekttyp
-apps/erpnext/erpnext/projects/doctype/task/task.py,Child Task exists for this Task. You can not delete this Task.,Für diese Aufgabe existiert eine untergeordnete Aufgabe. Sie können diese Aufgabe daher nicht löschen.
+apps/erpnext/erpnext/projects/doctype/task/task.py,Child Task exists for this Task. You can not delete this Task.,Für diesen Vorgang existiert ein untergeordneter Vorgang. Sie können diese Aufgabe daher nicht löschen.
apps/erpnext/erpnext/setup/doctype/sales_person/sales_person.py,Either target qty or target amount is mandatory.,Entweder Zielstückzahl oder Zielmenge ist zwingend erforderlich.
apps/erpnext/erpnext/config/projects.py,Cost of various activities,Aufwendungen für verschiedene Tätigkeiten
apps/erpnext/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py,"Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}","Einstellen Events auf {0}, da die Mitarbeiter auf die beigefügten unter Verkaufs Personen keine Benutzer-ID {1}"
@@ -5597,7 +5597,7 @@ apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py,Paid
apps/erpnext/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py,{0} is not a valid Batch Number for Item {1},{0} ist keine gültige Chargennummer für Artikel {1}
apps/erpnext/erpnext/shopping_cart/cart.py,Please enter valid coupon code !!,Bitte geben Sie einen gültigen Gutscheincode ein !!
apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,Note: There is not enough leave balance for Leave Type {0},Hinweis: Es gibt nicht genügend Urlaubsguthaben für Abwesenheitstyp {0}
-DocType: Task,Task Description,Aufgabenbeschreibung
+DocType: Task,Task Description,Vorgangsbeschreibung
DocType: Training Event,Seminar,Seminar
DocType: Program Enrollment Fee,Program Enrollment Fee,Programm Einschreibegebühr
DocType: Item,Supplier Items,Lieferantenartikel
@@ -5658,7 +5658,7 @@ apps/erpnext/erpnext/education/report/course_wise_assessment_report/course_wise_
DocType: Depreciation Schedule,Finance Book Id,Finanzbuch-ID
DocType: Item,Safety Stock,Sicherheitsbestand
DocType: Healthcare Settings,Healthcare Settings,Gesundheitswesen
-apps/erpnext/erpnext/hr/doctype/leave_application/leave_application_dashboard.html,Total Allocated Leaves,Insgesamt zugeteilte Blätter
+apps/erpnext/erpnext/hr/doctype/leave_application/leave_application_dashboard.html,Total Allocated Leaves,Insgesamt zugeteilte Urlaubstage
apps/erpnext/erpnext/projects/doctype/task/task.py,Progress % for a task cannot be more than 100.,Fortschritt-% eines Vorgangs darf nicht größer 100 sein.
DocType: Stock Reconciliation Item,Before reconciliation,Vor Ausgleich
apps/erpnext/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py,To {0},An {0}
@@ -5754,7 +5754,7 @@ apps/erpnext/erpnext/accounts/doctype/sales_invoice/pos.py,All Territories,Alle
DocType: Lost Reason Detail,Lost Reason Detail,Verlorene Begründung Detail
apps/erpnext/erpnext/hr/utils.py,Please set leave policy for employee {0} in Employee / Grade record,Legen Sie die Abwesenheitsrichtlinie für den Mitarbeiter {0} im Mitarbeiter- / Notensatz fest
apps/erpnext/erpnext/public/js/controllers/transaction.js,Invalid Blanket Order for the selected Customer and Item,Ungültiger Blankoauftrag für den ausgewählten Kunden und Artikel
-apps/erpnext/erpnext/projects/doctype/task/task_tree.js,Add Multiple Tasks,Mehrere Aufgaben hinzufügen
+apps/erpnext/erpnext/projects/doctype/task/task_tree.js,Add Multiple Tasks,Mehrere Vorgänge hinzufügen
DocType: Purchase Invoice,Items,Artikel
apps/erpnext/erpnext/crm/doctype/contract/contract.py,End Date cannot be before Start Date.,Das Enddatum darf nicht vor dem Startdatum liegen.
apps/erpnext/erpnext/education/doctype/course_enrollment/course_enrollment.py,Student is already enrolled.,Student ist bereits eingetragen sind.
@@ -5769,7 +5769,7 @@ apps/erpnext/erpnext/accounts/doctype/bank_account/test_bank_account.py,BankAcco
DocType: Normal Test Items,Normal Test Items,Normale Testartikel
DocType: QuickBooks Migrator,Company Settings,Unternehmenseinstellungen
DocType: Additional Salary,Overwrite Salary Structure Amount,Gehaltsstruktur überschreiben
-DocType: Leave Ledger Entry,Leaves,Blätter
+DocType: Leave Ledger Entry,Leaves,Urlaubstage
DocType: Student Language,Student Language,Student Sprache
DocType: Cash Flow Mapping,Is Working Capital,Ist Arbeitskapital
apps/erpnext/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js,Submit Proof,Nachweis einreichen
@@ -6362,7 +6362,7 @@ apps/erpnext/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax
DocType: Sales Partner,Contact Desc,Kontakt-Beschr.
DocType: Email Digest,Send regular summary reports via Email.,Regelmäßig zusammenfassende Berichte per E-Mail senden.
apps/erpnext/erpnext/hr/doctype/expense_claim/expense_claim.py,Please set default account in Expense Claim Type {0},Bitte setzen Sie Standardkonto in Kostenabrechnung Typ {0}
-apps/erpnext/erpnext/hr/doctype/leave_application/leave_application_dashboard.html,Available Leaves,Verfügbare Blätter
+apps/erpnext/erpnext/hr/doctype/leave_application/leave_application_dashboard.html,Available Leaves,Verfügbare Urlaubstage
DocType: Assessment Result,Student Name,Name des Studenten
DocType: Hub Tracked Item,Item Manager,Artikel-Manager
apps/erpnext/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py,Payroll Payable,Payroll Kreditoren
@@ -7508,7 +7508,7 @@ DocType: Pricing Rule,Percentage,Prozentsatz
apps/erpnext/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py,Item {0} must be a stock Item,Artikel {0} muss ein Lagerartikel sein
DocType: Manufacturing Settings,Default Work In Progress Warehouse,Standard-Fertigungslager
apps/erpnext/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.js,"Schedules for {0} overlaps, do you want to proceed after skiping overlaped slots ?","Schedules für {0} Überlappungen, möchten Sie nach Überlappung überlappender Slots fortfahren?"
-apps/erpnext/erpnext/hr/doctype/leave_period/leave_period.js,Grant Leaves,Grant Blätter
+apps/erpnext/erpnext/hr/doctype/leave_period/leave_period.js,Grant Leaves,Urlaubstage gewähren
DocType: Restaurant,Default Tax Template,Standardsteuervorlage
apps/erpnext/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py,{0} Students have been enrolled,{0} Studenten wurden angemeldet
DocType: Fees,Student Details,Studenten Details
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index f845cef9b10..20998108467 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -164,8 +164,8 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
qty_fields = [qty_fields]
distinct_uoms = list(set([d.get(uom_field) for d in doc.get_all_children()]))
- integer_uoms = filter(lambda uom: frappe.db.get_value("UOM", uom,
- "must_be_whole_number", cache=True) or None, distinct_uoms)
+ integer_uoms = list(filter(lambda uom: frappe.db.get_value("UOM", uom,
+ "must_be_whole_number", cache=True) or None, distinct_uoms))
if not integer_uoms:
return
diff --git a/erpnext/utilities/user_progress.py b/erpnext/utilities/user_progress.py
deleted file mode 100644
index 5cec3ca3846..00000000000
--- a/erpnext/utilities/user_progress.py
+++ /dev/null
@@ -1,287 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe, erpnext
-from frappe import _
-from erpnext.setup.doctype.setup_progress.setup_progress import get_action_completed_state
-
-def get_slide_settings():
- defaults = frappe.defaults.get_defaults()
- domain = frappe.get_cached_value('Company', erpnext.get_default_company(), 'domain')
- company = defaults.get("company") or ''
- currency = defaults.get("currency") or ''
-
- doc = frappe.get_doc("Setup Progress")
- item = [d for d in doc.get("actions") if d.action_name == "Set Sales Target"]
-
- if len(item):
- item = item[0]
- if not item.action_document:
- item.action_document = company
- doc.save()
-
- # Initial state of slides
- return [
- frappe._dict(
- action_name='Add Company',
- title=_("Setup Company") if domain != 'Education' else _("Setup Institution"),
- help=_('Setup your ' + ('company' if domain != 'Education' else 'institution') + ' and brand.'),
- # image_src="/assets/erpnext/images/illustrations/shop.jpg",
- fields=[],
- done_state_title=_("You added " + company),
- done_state_title_route=["Form", "Company", company],
- help_links=[
- {
- "label": _("Chart of Accounts"),
- "url": ["https://erpnext.com/docs/user/manual/en/accounts/chart-of-accounts"]
- },
- {
- "label": _("Opening Balances"),
- "video_id": "U5wPIvEn-0c"
- }
- ]
- ),
- frappe._dict(
- action_name='Set Sales Target',
- domains=('Manufacturing', 'Services', 'Retail', 'Distribution'),
- title=_("Set a Target"),
- help=_("Set a sales goal you'd like to achieve for your company."),
- fields=[
- {"fieldtype":"Currency", "fieldname":"monthly_sales_target",
- "label":_("Monthly Sales Target (" + currency + ")"), "reqd":1},
- ],
- submit_method="erpnext.utilities.user_progress_utils.set_sales_target",
- done_state_title=_("Go to " + company),
- done_state_title_route=["Form", "Company", company],
- help_links=[
- {
- "label": _('Learn More'),
- "url": ["https://erpnext.com/docs/user/manual/en/setting-up/setting-company-sales-goal"]
- }
- ]
- ),
- frappe._dict(
- action_name='Add Customers',
- domains=('Manufacturing', 'Services', 'Retail', 'Distribution'),
- title=_("Add Customers"),
- help=_("List a few of your customers. They could be organizations or individuals."),
- fields=[
- {"fieldtype":"Section Break"},
- {"fieldtype":"Data", "fieldname":"customer", "label":_("Customer"),
- "placeholder":_("Customer Name")},
- {"fieldtype":"Column Break"},
- {"fieldtype":"Data", "fieldname":"customer_contact",
- "label":_("Contact Name"), "placeholder":_("Contact Name")}
- ],
- add_more=1, max_count=3, mandatory_entry=1,
- submit_method="erpnext.utilities.user_progress_utils.create_customers",
- done_state_title=_("Go to Customers"),
- done_state_title_route=["List", "Customer"],
- help_links=[
- {
- "label": _('Learn More'),
- "url": ["https://erpnext.com/docs/user/manual/en/CRM/customer.html"]
- }
- ]
- ),
-
- frappe._dict(
- action_name='Add Letterhead',
- domains=('Manufacturing', 'Services', 'Retail', 'Distribution', 'Education'),
- title=_("Add Letterhead"),
- help=_("Upload your letter head (Keep it web friendly as 900px by 100px)"),
- fields=[
- {"fieldtype":"Attach Image", "fieldname":"letterhead",
- "is_private": 0,
- "align": "center"
- },
- ],
- mandatory_entry=1,
- submit_method="erpnext.utilities.user_progress_utils.create_letterhead",
- done_state_title=_("Go to Letterheads"),
- done_state_title_route=["List", "Letter Head"]
- ),
-
- frappe._dict(
- action_name='Add Suppliers',
- domains=('Manufacturing', 'Services', 'Retail', 'Distribution'),
- icon="fa fa-group",
- title=_("Your Suppliers"),
- help=_("List a few of your suppliers. They could be organizations or individuals."),
- fields=[
- {"fieldtype":"Section Break"},
- {"fieldtype":"Data", "fieldname":"supplier", "label":_("Supplier"),
- "placeholder":_("Supplier Name")},
- {"fieldtype":"Column Break"},
- {"fieldtype":"Data", "fieldname":"supplier_contact",
- "label":_("Contact Name"), "placeholder":_("Contact Name")},
- ],
- add_more=1, max_count=3, mandatory_entry=1,
- submit_method="erpnext.utilities.user_progress_utils.create_suppliers",
- done_state_title=_("Go to Suppliers"),
- done_state_title_route=["List", "Supplier"],
- help_links=[
- {
- "label": _('Learn More'),
- "url": ["https://erpnext.com/docs/user/manual/en/buying/supplier"]
- },
- {
- "label": _('Customers and Suppliers'),
- "video_id": "zsrrVDk6VBs"
- },
- ]
- ),
- frappe._dict(
- action_name='Add Products',
- domains=['Manufacturing', 'Services', 'Retail', 'Distribution'],
- icon="fa fa-barcode",
- title=_("Your Products or Services"),
- help=_("List your products or services that you buy or sell."),
- fields=[
- {"fieldtype":"Section Break", "show_section_border": 1},
- {"fieldtype":"Data", "fieldname":"item", "label":_("Item"),
- "placeholder":_("A Product")},
- {"fieldtype":"Column Break"},
- {"fieldtype":"Select", "fieldname":"item_uom", "label":_("UOM"),
- "options":[_("Unit"), _("Nos"), _("Box"), _("Pair"), _("Kg"), _("Set"),
- _("Hour"), _("Minute"), _("Litre"), _("Meter"), _("Gram")],
- "default": _("Unit"), "static": 1},
- {"fieldtype":"Column Break"},
- {"fieldtype":"Currency", "fieldname":"item_price", "label":_("Rate"), "static": 1}
- ],
- add_more=1, max_count=3, mandatory_entry=1,
- submit_method="erpnext.utilities.user_progress_utils.create_items",
- done_state_title=_("Go to Items"),
- done_state_title_route=["List", "Item"],
- help_links=[
- {
- "label": _("Explore Sales Cycle"),
- "video_id": "1eP90MWoDQM"
- },
- ]
- ),
-
- # Education slides begin
- frappe._dict(
- action_name='Add Programs',
- domains=("Education"),
- title=_("Program"),
- help=_("Example: Masters in Computer Science"),
- fields=[
- {"fieldtype":"Section Break", "show_section_border": 1},
- {"fieldtype":"Data", "fieldname":"program", "label":_("Program"), "placeholder": _("Program Name")},
- ],
- add_more=1, max_count=3, mandatory_entry=1,
- submit_method="erpnext.utilities.user_progress_utils.create_program",
- done_state_title=_("Go to Programs"),
- done_state_title_route=["List", "Program"],
- help_links=[
- {
- "label": _("Student Application"),
- "video_id": "l8PUACusN3E"
- },
- ]
-
- ),
- frappe._dict(
- action_name='Add Courses',
- domains=["Education"],
- title=_("Course"),
- help=_("Example: Basic Mathematics"),
- fields=[
- {"fieldtype":"Section Break", "show_section_border": 1},
- {"fieldtype":"Data", "fieldname":"course", "label":_("Course"), "placeholder": _("Course Name")},
- ],
- add_more=1, max_count=3, mandatory_entry=1,
- submit_method="erpnext.utilities.user_progress_utils.create_course",
- done_state_title=_("Go to Courses"),
- done_state_title_route=["List", "Course"],
- help_links=[
- {
- "label": _('Add Students'),
- "route": ["List", "Student"]
- }
- ]
- ),
- frappe._dict(
- action_name='Add Instructors',
- domains=["Education"],
- title=_("Instructor"),
- help=_("People who teach at your organisation"),
- fields=[
- {"fieldtype":"Section Break", "show_section_border": 1},
- {"fieldtype":"Data", "fieldname":"instructor", "label":_("Instructor"), "placeholder": _("Instructor Name")},
- ],
- add_more=1, max_count=3, mandatory_entry=1,
- submit_method="erpnext.utilities.user_progress_utils.create_instructor",
- done_state_title=_("Go to Instructors"),
- done_state_title_route=["List", "Instructor"],
- help_links=[
- {
- "label": _('Student Batches'),
- "route": ["List", "Student Batch"]
- }
- ]
- ),
- frappe._dict(
- action_name='Add Rooms',
- domains=["Education"],
- title=_("Room"),
- help=_("Classrooms/ Laboratories etc where lectures can be scheduled."),
- fields=[
- {"fieldtype":"Section Break", "show_section_border": 1},
- {"fieldtype":"Data", "fieldname":"room", "label":_("Room")},
- {"fieldtype":"Column Break"},
- {"fieldtype":"Int", "fieldname":"room_capacity", "label":_("Room Capacity"), "static": 1},
- ],
- add_more=1, max_count=3, mandatory_entry=1,
- submit_method="erpnext.utilities.user_progress_utils.create_room",
- done_state_title=_("Go to Rooms"),
- done_state_title_route=["List", "Room"],
- help_links=[]
- ),
- # Education slides end
-
- frappe._dict(
- action_name='Add Users',
- title=_("Add Users"),
- help=_("Add users to your organization, other than yourself."),
- fields=[
- {"fieldtype":"Section Break"},
- {"fieldtype":"Data", "fieldname":"user_email", "label":_("Email ID"),
- "placeholder":_("user@example.com"), "options": "Email", "static": 1},
- {"fieldtype":"Column Break"},
- {"fieldtype":"Data", "fieldname":"user_fullname",
- "label":_("Full Name"), "static": 1},
- ],
- add_more=1, max_count=3, mandatory_entry=1,
- submit_method="erpnext.utilities.user_progress_utils.create_users",
- done_state_title=_("Go to Users"),
- done_state_title_route=["List", "User"],
- help_links=[
- {
- "label": _('Learn More'),
- "url": ["https://erpnext.com/docs/user/manual/en/setting-up/users-and-permissions"]
- },
- {
- "label": _('Users and Permissions'),
- "video_id": "8Slw1hsTmUI"
- },
- ]
- )
- ]
-
-def get_user_progress_slides():
- slides = []
- slide_settings = get_slide_settings()
-
- domains = frappe.get_active_domains()
- for s in slide_settings:
- if not s.domains or any(d in domains for d in s.domains):
- s.mark_as_done_method = "erpnext.setup.doctype.setup_progress.setup_progress.set_action_completed_state"
- s.done = get_action_completed_state(s.action_name) or 0
- slides.append(s)
-
- return slides
-
diff --git a/erpnext/utilities/user_progress_utils.py b/erpnext/utilities/user_progress_utils.py
deleted file mode 100644
index b7c24a71ba6..00000000000
--- a/erpnext/utilities/user_progress_utils.py
+++ /dev/null
@@ -1,240 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe, erpnext
-
-import json
-from frappe import _
-from frappe.utils import flt
-from erpnext.setup.doctype.setup_progress.setup_progress import update_domain_actions, get_domain_actions_state
-
-@frappe.whitelist()
-def set_sales_target(args_data):
- args = json.loads(args_data)
- defaults = frappe.defaults.get_defaults()
- frappe.db.set_value("Company", defaults.get("company"), "monthly_sales_target", args.get('monthly_sales_target'))
-
-@frappe.whitelist()
-def create_customers(args_data):
- args = json.loads(args_data)
- defaults = frappe.defaults.get_defaults()
- for i in range(1,4):
- customer = args.get("customer_" + str(i))
- if customer:
- try:
- doc = frappe.get_doc({
- "doctype":"Customer",
- "customer_name": customer,
- "customer_type": "Company",
- "customer_group": _("Commercial"),
- "territory": defaults.get("country"),
- "company": defaults.get("company")
- }).insert()
-
- if args.get("customer_contact_" + str(i)):
- create_contact(args.get("customer_contact_" + str(i)),
- "Customer", doc.name)
- except frappe.NameError:
- pass
-
-@frappe.whitelist()
-def create_letterhead(args_data):
- args = json.loads(args_data)
- letterhead = args.get("letterhead")
- if letterhead:
- try:
- frappe.get_doc({
- "doctype":"Letter Head",
- "content":"""
+
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js
new file mode 100644
index 00000000000..262e31b3e40
--- /dev/null
+++ b/erpnext/www/book_appointment/index.js
@@ -0,0 +1,231 @@
+frappe.ready(async () => {
+ initialise_select_date();
+})
+
+window.holiday_list = [];
+
+async function initialise_select_date() {
+ navigate_to_page(1);
+ await get_global_variables();
+ setup_date_picker();
+ setup_timezone_selector();
+ hide_next_button();
+}
+
+async function get_global_variables() {
+ // Using await through this file instead of then.
+ window.appointment_settings = (await frappe.call({
+ method: 'erpnext.www.book_appointment.index.get_appointment_settings'
+ })).message;
+ window.timezones = (await frappe.call({
+ method:'erpnext.www.book_appointment.index.get_timezones'
+ })).message;
+ window.holiday_list = window.appointment_settings.holiday_list;
+}
+
+function setup_timezone_selector() {
+ let timezones_element = document.getElementById('appointment-timezone');
+ let local_timezone = moment.tz.guess()
+ window.timezones.forEach(timezone => {
+ let opt = document.createElement('option');
+ opt.value = timezone;
+ if (timezone == local_timezone) {
+ opt.selected = true;
+ }
+ opt.innerHTML = timezone;
+ timezones_element.appendChild(opt)
+ });
+}
+
+function setup_date_picker() {
+ let date_picker = document.getElementById('appointment-date');
+ let today = new Date();
+ date_picker.min = today.toISOString().substr(0, 10);
+ today.setDate(today.getDate() + window.appointment_settings.advance_booking_days);
+ date_picker.max = today.toISOString().substr(0, 10);
+}
+
+function hide_next_button() {
+ let next_button = document.getElementById('next-button');
+ next_button.disabled = true;
+ next_button.onclick = () => frappe.msgprint("Please select a date and time");
+}
+
+function show_next_button() {
+ let next_button = document.getElementById('next-button');
+ next_button.disabled = false;
+ next_button.onclick = setup_details_page;
+}
+
+function on_date_or_timezone_select() {
+ let date_picker = document.getElementById('appointment-date');
+ let timezone = document.getElementById('appointment-timezone');
+ if (date_picker.value === '') {
+ clear_time_slots();
+ hide_next_button();
+ frappe.throw('Please select a date');
+ }
+ window.selected_date = date_picker.value;
+ window.selected_timezone = timezone.value;
+ update_time_slots(date_picker.value, timezone.value);
+ let lead_text = document.getElementById('lead-text');
+ lead_text.innerHTML = "Select Time"
+}
+
+async function get_time_slots(date, timezone) {
+ let slots = (await frappe.call({
+ method: 'erpnext.www.book_appointment.index.get_appointment_slots',
+ args: {
+ date: date,
+ timezone: timezone
+ }
+ })).message;
+ return slots;
+}
+
+async function update_time_slots(selected_date, selected_timezone) {
+ let timeslot_container = document.getElementById('timeslot-container');
+ window.slots = await get_time_slots(selected_date, selected_timezone);
+ clear_time_slots();
+ if (window.slots.length <= 0) {
+ let message_div = document.createElement('p');
+ message_div.innerHTML = "There are no slots available on this date";
+ timeslot_container.appendChild(message_div);
+ return
+ }
+ window.slots.forEach((slot, index) => {
+ // Get and append timeslot div
+ let timeslot_div = get_timeslot_div_layout(slot)
+ timeslot_container.appendChild(timeslot_div);
+ });
+ set_default_timeslot();
+}
+
+function get_timeslot_div_layout(timeslot) {
+ let start_time = new Date(timeslot.time)
+ let timeslot_div = document.createElement('div');
+ timeslot_div.classList.add('time-slot');
+ if (!timeslot.availability) {
+ timeslot_div.classList.add('unavailable')
+ }
+ timeslot_div.innerHTML = get_slot_layout(start_time);
+ timeslot_div.id = timeslot.time.substring(11, 19);
+ timeslot_div.addEventListener('click', select_time);
+ return timeslot_div
+}
+
+function clear_time_slots() {
+ // Clear any existing divs in timeslot container
+ let timeslot_container = document.getElementById('timeslot-container');
+ while (timeslot_container.firstChild) {
+ timeslot_container.removeChild(timeslot_container.firstChild);
+ }
+}
+
+function get_slot_layout(time) {
+ let timezone = document.getElementById("appointment-timezone").value;
+ time = new Date(time);
+ let start_time_string = moment(time).tz(timezone).format("LT");
+ let end_time = moment(time).tz(timezone).add(window.appointment_settings.appointment_duration, 'minutes');
+ let end_time_string = end_time.format("LT");
+ return `${start_time_string}
+
+
+
+
+
+
+
+ Book an appointment+Select the date and your timezone +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add details+Selected date is at + +
+
+
+
+
+
+
+
+
+ to ${end_time_string}`; +} + +function select_time() { + if (this.classList.contains('unavailable')) { + return; + } + let selected_element = document.getElementsByClassName('selected'); + if (!(selected_element.length > 0)) { + this.classList.add('selected'); + show_next_button(); + return; + } + selected_element = selected_element[0] + window.selected_time = this.id; + selected_element.classList.remove('selected'); + this.classList.add('selected'); + show_next_button(); +} + +function set_default_timeslot() { + let timeslots = document.getElementsByClassName('time-slot') + // Can't use a forEach here since, we need to break the loop after a timeslot is selected + for (let i = 0; i < timeslots.length; i++) { + const timeslot = timeslots[i]; + if (!timeslot.classList.contains('unavailable')) { + timeslot.classList.add('selected'); + break; + } + } +} + +function navigate_to_page(page_number) { + let page1 = document.getElementById('select-date-time'); + let page2 = document.getElementById('enter-details'); + switch (page_number) { + case 1: + page1.style.display = 'block'; + page2.style.display = 'none'; + break; + case 2: + page1.style.display = 'none'; + page2.style.display = 'block'; + break; + default: + break; + } +} + +function setup_details_page() { + navigate_to_page(2) + let date_container = document.getElementsByClassName('date-span')[0]; + let time_container = document.getElementsByClassName('time-span')[0]; + date_container.innerHTML = moment(window.selected_date).format("MMM Do YYYY"); + time_container.innerHTML = moment(window.selected_time, "HH:mm:ss").format("LT"); +} + +async function submit() { + let button = document.getElementById('submit-button'); + button.disabled = true; + let form = document.querySelector('#customer-form'); + if (!form.checkValidity()) { + form.reportValidity(); + button.disabled = false; + return; + } + let contact = get_form_data(); + let appointment = frappe.call({ + method: 'erpnext.www.book_appointment.index.create_appointment', + args: { + 'date': window.selected_date, + 'time': window.selected_time, + 'contact': contact, + 'tz':window.selected_timezone + }, + callback: (response)=>{ + if (response.message.status == "Unverified") { + frappe.show_alert("Please check your email to confirm the appointment") + } else { + frappe.show_alert("Appointment Created Successfully"); + } + setTimeout(()=>{ + let redirect_url = "/"; + if (window.appointment_settings.success_redirect_url){ + redirect_url += window.appointment_settings.success_redirect_url; + } + window.location.href = redirect_url;},5000) + }, + error: (err)=>{ + frappe.show_alert("Something went wrong please try again"); + button.disabled = false; + } + }); +} + +function get_form_data() { + contact = {}; + let inputs = ['name', 'skype', 'number', 'notes', 'email']; + inputs.forEach((id) => contact[id] = document.getElementById(`customer_${id}`).value) + return contact +} diff --git a/erpnext/www/book_appointment/index.py b/erpnext/www/book_appointment/index.py new file mode 100644 index 00000000000..7bfac89f308 --- /dev/null +++ b/erpnext/www/book_appointment/index.py @@ -0,0 +1,149 @@ +import frappe +import datetime +import json +import pytz + + +WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + +no_cache = 1 + + +def get_context(context): + is_enabled = frappe.db.get_single_value('Appointment Booking Settings', 'enable_scheduling') + if is_enabled: + return context + else: + frappe.local.flags.redirect_location = '/404' + raise frappe.Redirect + +@frappe.whitelist(allow_guest=True) +def get_appointment_settings(): + settings = frappe.get_doc('Appointment Booking Settings') + settings.holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) + return settings + +@frappe.whitelist(allow_guest=True) +def get_timezones(): + import pytz + return pytz.all_timezones + +@frappe.whitelist(allow_guest=True) +def get_appointment_slots(date, timezone): + # Convert query to local timezones + format_string = '%Y-%m-%d %H:%M:%S' + query_start_time = datetime.datetime.strptime(date + ' 00:00:00', format_string) + query_end_time = datetime.datetime.strptime(date + ' 23:59:59', format_string) + query_start_time = convert_to_system_timezone(timezone, query_start_time) + query_end_time = convert_to_system_timezone(timezone, query_end_time) + now = convert_to_guest_timezone(timezone, datetime.datetime.now()) + + # Database queries + settings = frappe.get_doc('Appointment Booking Settings') + holiday_list = frappe.get_doc('Holiday List', settings.holiday_list) + timeslots = get_available_slots_between(query_start_time, query_end_time, settings) + + # Filter and convert timeslots + converted_timeslots = [] + for timeslot in timeslots: + converted_timeslot = convert_to_guest_timezone(timezone, timeslot) + # Check if holiday + if _is_holiday(converted_timeslot.date(), holiday_list): + converted_timeslots.append(dict(time=converted_timeslot, availability=False)) + continue + # Check availability + if check_availabilty(timeslot, settings) and converted_timeslot >= now: + converted_timeslots.append(dict(time=converted_timeslot, availability=True)) + else: + converted_timeslots.append(dict(time=converted_timeslot, availability=False)) + date_required = datetime.datetime.strptime(date + ' 00:00:00', format_string).date() + converted_timeslots = filter_timeslots(date_required, converted_timeslots) + return converted_timeslots + +def get_available_slots_between(query_start_time, query_end_time, settings): + records = _get_records(query_start_time, query_end_time, settings) + timeslots = [] + appointment_duration = datetime.timedelta( + minutes=settings.appointment_duration) + for record in records: + if record.day_of_week == WEEKDAYS[query_start_time.weekday()]: + current_time = _deltatime_to_datetime(query_start_time, record.from_time) + end_time = _deltatime_to_datetime(query_start_time, record.to_time) + else: + current_time = _deltatime_to_datetime(query_end_time, record.from_time) + end_time = _deltatime_to_datetime(query_end_time, record.to_time) + while current_time + appointment_duration <= end_time: + timeslots.append(current_time) + current_time += appointment_duration + return timeslots + + +@frappe.whitelist(allow_guest=True) +def create_appointment(date, time, tz, contact): + format_string = '%Y-%m-%d %H:%M:%S' + scheduled_time = datetime.datetime.strptime(date + " " + time, format_string) + # Strip tzinfo from datetime objects since it's handled by the doctype + scheduled_time = scheduled_time.replace(tzinfo = None) + scheduled_time = convert_to_system_timezone(tz, scheduled_time) + scheduled_time = scheduled_time.replace(tzinfo = None) + # Create a appointment document from form + appointment = frappe.new_doc('Appointment') + appointment.scheduled_time = scheduled_time + contact = json.loads(contact) + appointment.customer_name = contact.get('name', None) + appointment.customer_phone_number = contact.get('number', None) + appointment.customer_skype = contact.get('skype', None) + appointment.customer_details = contact.get('notes', None) + appointment.customer_email = contact.get('email', None) + appointment.status = 'Open' + appointment.insert() + return appointment + +# Helper Functions +def filter_timeslots(date, timeslots): + filtered_timeslots = [] + for timeslot in timeslots: + if(timeslot['time'].date() == date): + filtered_timeslots.append(timeslot) + return filtered_timeslots + +def convert_to_guest_timezone(guest_tz, datetimeobject): + guest_tz = pytz.timezone(guest_tz) + local_timezone = pytz.timezone(frappe.utils.get_time_zone()) + datetimeobject = local_timezone.localize(datetimeobject) + datetimeobject = datetimeobject.astimezone(guest_tz) + return datetimeobject + +def convert_to_system_timezone(guest_tz,datetimeobject): + guest_tz = pytz.timezone(guest_tz) + datetimeobject = guest_tz.localize(datetimeobject) + system_tz = pytz.timezone(frappe.utils.get_time_zone()) + datetimeobject = datetimeobject.astimezone(system_tz) + return datetimeobject + +def check_availabilty(timeslot, settings): + return frappe.db.count('Appointment', {'scheduled_time': timeslot}) < settings.number_of_agents + +def _is_holiday(date, holiday_list): + for holiday in holiday_list.holidays: + if holiday.holiday_date == date: + return True + return False + + +def _get_records(start_time, end_time, settings): + records = [] + for record in settings.availability_of_slots: + if record.day_of_week == WEEKDAYS[start_time.weekday()] or record.day_of_week == WEEKDAYS[end_time.weekday()]: + records.append(record) + return records + + +def _deltatime_to_datetime(date, deltatime): + time = (datetime.datetime.min + deltatime).time() + return datetime.datetime.combine(date.date(), time) + + +def _datetime_to_deltatime(date_time): + midnight = datetime.datetime.combine(date_time.date(), datetime.time.min) + return (date_time-midnight) \ No newline at end of file diff --git a/erpnext/www/book_appointment/verify/__init__.py b/erpnext/www/book_appointment/verify/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/www/book_appointment/verify/index.html b/erpnext/www/book_appointment/verify/index.html new file mode 100644 index 00000000000..ebb65b1f24e --- /dev/null +++ b/erpnext/www/book_appointment/verify/index.html @@ -0,0 +1,18 @@ +{% extends "templates/web.html" %} + +{% block title %} +{{ _("Verify Email") }} +{% endblock%} + +{% block page_content %} + + {% if success==True %} +
+ Your email has been verified and your appointment has been scheduled
+
+ {% else %}
+
+ Verification failed please check the link
+
+ {% endif %}
+{% endblock%}
\ No newline at end of file
diff --git a/erpnext/www/book_appointment/verify/index.py b/erpnext/www/book_appointment/verify/index.py
new file mode 100644
index 00000000000..d4478ae34a8
--- /dev/null
+++ b/erpnext/www/book_appointment/verify/index.py
@@ -0,0 +1,20 @@
+import frappe
+
+from frappe.utils.verified_command import verify_request
+@frappe.whitelist(allow_guest=True)
+def get_context(context):
+ if not verify_request():
+ context.success = False
+ return context
+
+ email = frappe.form_dict['email']
+ appointment_name = frappe.form_dict['appointment']
+
+ if email and appointment_name:
+ appointment = frappe.get_doc('Appointment',appointment_name)
+ appointment.set_verified(email)
+ context.success = True
+ return context
+ else:
+ context.success = False
+ return context
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 28ba9f676ff..c277545fab5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,10 @@
+braintree==3.57.1
frappe
-unidecode
-pygithub
-googlemaps
-python-stdnum
-braintree
-gocardless_pro
-woocommerce
-pandas
-plaid-python
\ No newline at end of file
+gocardless-pro==1.11.0
+googlemaps==3.1.1
+pandas==0.24.2
+plaid-python==3.4.0
+PyGithub==1.44.1
+python-stdnum==1.12
+Unidecode==1.1.1
+WooCommerce==2.1.1
|